Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a7522b4
feat(state): add state class registry v1
Kucell Sep 28, 2026
1142913
feat(state): add state class registry schema
Kucell Sep 28, 2026
a053141
feat(state): add state class registry loader
Kucell Sep 28, 2026
6c3816d
refactor(state-sync): derive sync paths from state registry
Kucell Sep 28, 2026
c3d9683
feat(governance): add deterministic index projector
Kucell Sep 28, 2026
22018f9
feat(governance): add index verify and rebuild CLI
Kucell Sep 28, 2026
f1e080f
feat(cli): wire governance index command
Kucell Sep 28, 2026
85f2900
docs(cli): register governance index command
Kucell Sep 28, 2026
dd6fed3
refactor(githook): consume state class registry
Kucell Sep 28, 2026
a17dac2
test(state): cover state class registry v1
Kucell Sep 28, 2026
4bd4f06
test(governance): cover deterministic index projection
Kucell Sep 28, 2026
9bdd5e2
test(state-sync): align assertions with state registry
Kucell Sep 28, 2026
811d8f5
test(governance): add index CLI integration
Kucell Sep 28, 2026
b997c9a
fix(state): fail closed on invalid registry metadata
Kucell Sep 28, 2026
959ba5e
refactor(governance): keep verify output bounded
Kucell Sep 28, 2026
9a9039c
test(state): satisfy registry metadata in duplicate-path fixture
Kucell Sep 28, 2026
3df40ee
fix(governance): rollback partial index replacement
Kucell Sep 28, 2026
a997763
test(state-sync): keep local evidence and legacy state unsynced
Kucell Sep 28, 2026
8eff903
test(state): verify pre-commit consumes registry policy
Kucell Sep 28, 2026
720cec3
ci(m042): add state registry and index gate
Kucell Sep 28, 2026
735d0bb
fix(cli): dispatch governance index command
Kucell Sep 28, 2026
91b905a
docs(state-sync): point to registry source of truth
Kucell Sep 28, 2026
6c56f36
docs(cli): describe registry-managed state sync
Kucell Sep 28, 2026
9b764b7
docs(githook): document state registry source of truth
Kucell Sep 28, 2026
b4c6238
fix(governance): keep waitpoint index writes schema-aligned
Kucell Sep 28, 2026
a5b5102
test(governance): assert waitpoint index contract on write
Kucell Sep 28, 2026
8ab0f44
test(governance): lock waitpoint writer index contract
Kucell Sep 28, 2026
84cf727
ci(m042): lock waitpoint projection writer contract
Kucell Sep 28, 2026
fd64517
chore(m042): sync reconciled M-041 before revalidation
Kucell Sep 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions .github/workflows/m042-state-registry-index.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: M-042 State Registry and Index

on:
pull_request:
paths:
- "bin/cli.js"
- "lib/cli/contract.js"
- "lib/state-registry/**"
- "lib/state-sync/**"
- "lib/governance-index/**"
- "templates/_shared/.agent/contracts/state-classes*"
- "templates/_shared/.agent/.githooks/pre-commit"
- "tests/state-registry/**"
- "tests/state-sync/**"
- "tests/governance-index/**"
- "tests/management/management-writer-cli.test.js"
- "tests/cli/issue-15-waitpoints-help-state-sync.test.js"
- ".github/workflows/m042-state-registry-index.yml"
workflow_dispatch:

jobs:
focused:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: [18, 22, 24]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- name: State Class Registry
run: node --test tests/state-registry/*.test.js
- name: Governance index
run: node --test tests/governance-index/*.test.js
- name: State Sync regression
run: |
node --test tests/state-sync/state-sync.test.js
node --test tests/state-sync/state-sync-auto.test.js
node --test tests/install/install-state-githooks.test.js
- name: M-041 exact-sync regression
run: node --test tests/cli/issue-15-waitpoints-help-state-sync.test.js
- name: Management writer projection contract
run: node --test tests/management/management-writer-cli.test.js
- name: Syntax and module smoke
run: |
bash -n templates/_shared/.agent/.githooks/pre-commit
node --check lib/governance-index/index.js
node --check lib/governance-index/cli.js
node -e "require('./lib/state-registry'); require('./lib/state-sync'); require('./lib/governance-index')"
- name: Package contract smoke
run: |
npm pack --dry-run --json > /tmp/m042-pack.json
node - <<'NODE'
const fs = require("fs");
const data = JSON.parse(fs.readFileSync("/tmp/m042-pack.json", "utf8"));
const files = new Set((data[0].files || []).map((entry) => entry.path));
for (const required of [
"templates/_shared/.agent/contracts/state-classes.json",
"templates/_shared/.agent/contracts/state-classes.schema.json",
"lib/state-registry/index.js",
"lib/governance-index/index.js",
"lib/governance-index/cli.js",
]) {
if (!files.has(required)) throw new Error("package missing " + required);
}
NODE
15 changes: 11 additions & 4 deletions bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,9 @@ const { eventBusCommand } = require("../lib/event-bus/cli");

// T-FOLLOW-002 v2: `.agent/` state sync CLI surface.
// `state-sync [--dry-run|--add|--commit|--push]` lives in
// lib/state-sync.js. It scans the 9 state-class directories
// (decisions/ waitpoints/ tasks/ missions/ plans/ dispatch/ workflows/
// skills/ branches/registry.json) inside the inner .agent/ git repo
// and stages/commits/pushes them so project-management state stays
// lib/state-sync/index.js. It resolves syncable state through the versioned
// State Class Registry contract in templates/_shared/.agent/contracts/
// state-classes.json and stages/commits/pushes that project state so it stays
// in lock-step across machines. Strictly additive: no changes to
// lib/commands.js; the new subcommand is added to the case dispatch below.
//
Expand All @@ -159,6 +158,7 @@ const { eventBusCommand } = require("../lib/event-bus/cli");
// bin/cli.js (not lib/commands.js) so M-001 shadow-init's invariant
// "lib/commands.js has 0 changes vs base f8a1d38" stays intact.
const { stateSync, installStateGithooks, fireAndForgetSync } = require("../lib/state-sync/index.js");
const { governanceIndexCommand } = require("../lib/governance-index/cli.js");
const { shouldAutoSyncCoordination } = require("../lib/commands/management/coordination.js");

// GitHub issue #15: decisions / inbox / waitpoints write wrappers now return
Expand Down Expand Up @@ -734,6 +734,13 @@ async function initModeGeneral() {
case "pr": prCommand(ctx); break;
case "event-bus": eventBusCommand(ctx); break;
case "state-sync": await stateSync(l1Ctx); break;
case "governance-index": {
const result = governanceIndexCommand(ctx);
if (result && result.ok && result.effect && result.effect.kind === "mutation" && result.effect.committed) {
fireAndForgetSync({ ...l1Ctx, cwd: result.project_root }, { paths: result.effect.paths }).catch(() => {});
}
break;
}
case "help": args.includes("--json") ? cliHelp(ctx) : printHelp(); break;
case "dev": await dev(ctx); break;
case undefined:
Expand Down
3 changes: 2 additions & 1 deletion lib/cli/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ const commands = [
),
command("branch", "branch <subcommand> [options]", "Git branch lifecycle management for cortex-agent-managed refs.", { mode: "branch_management" }),
command("pr", "pr merge <pr-number> --gate user [options]", "Explicitly merge a pull request through the existing vcs-pr runtime. Never automatic; requires user gate.", { mode: "user_gated_vcs", implemented: true, automatic_merge_enabled: false, requires: [".agent/config/vcs.yml", "--gate user"] }),
command("state-sync", "state-sync [--dry-run|--add|--commit|--push]", "Scan the state-class directories and stage/commit/push project-management state so it stays in lock-step across machines.", { mode: "state_sync" }),
command("state-sync", "state-sync [--dry-run|--add|--commit|--push]", "Scan registry-managed state classes and stage/commit/push project-management state so it stays in lock-step across machines.", { mode: "state_sync" }),
command("governance-index", "governance-index <verify|rebuild> [--project <path>]", "Deterministically project Decision/Waitpoint indexes from authoritative records. verify is read-only; rebuild writes only the two index projections and fails closed on malformed sources.", { mode: "governance_projection", implemented: true }),
];

const options = [
Expand Down
92 changes: 92 additions & 0 deletions lib/governance-index/cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"use strict";

const path = require("node:path");
const {
verifyGovernanceIndexes,
rebuildGovernanceIndexes,
} = require("./index.js");
const {
commandNone,
commandRead,
commandMutation,
commandFailure,
} = require("../cli/effect.js");

function usage() {
return [
"Usage:",
" cortex-agent governance-index verify [--project <path>] [--json]",
" cortex-agent governance-index rebuild [--project <path>] [--json]",
"",
"verify Read-only: project Decision/Waitpoint indexes from authoritative records and report drift.",
"rebuild Write only decisions/index.json and waitpoints/index.json; fails closed if a source cannot be safely projected.",
].join("\n");
}

function targetRoot(ctx) {
if (ctx.options && ctx.options.project) return path.resolve(ctx.cwd, ctx.options.project);
return ctx.cwd;
}

function print(payload) {
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
}

function governanceIndexCommand(ctx) {
const sub = ctx.args[1];
if (!sub || sub === "help" || ctx.args.includes("--help") || ctx.args.includes("-h")) {
process.stdout.write(usage() + "\n");
return { ...commandNone({ help: true }), project_root: targetRoot(ctx) };
}

const projectRoot = targetRoot(ctx);
if (sub === "verify") {
const result = verifyGovernanceIndexes(projectRoot);
print({ action: "governance-index verify", ...result });
if (!result.ok) process.exitCode = 1;
return {
...commandRead({
resources: ["projection:decisions-index", "projection:waitpoints-index"],
domains: ["filesystem"],
}),
ok: result.ok,
project_root: projectRoot,
verification: result,
};
}

if (sub === "rebuild") {
const result = rebuildGovernanceIndexes(projectRoot);
if (!result.ok) {
print({ action: "governance-index rebuild", ...result });
process.exitCode = 3;
return {
...commandFailure(result.code || "GOVERNANCE_INDEX_REBUILD_FAILED"),
project_root: projectRoot,
rebuild: result,
};
}
print({ action: "governance-index rebuild", ...result });
return {
...commandMutation({
committed: true,
exact_paths: true,
resources: result.changed_resources,
paths: result.changed_paths,
domains: ["filesystem"],
}),
project_root: projectRoot,
rebuild: result,
};
}

process.stderr.write("governance-index: unknown subcommand: " + sub + "\n");
process.stderr.write(usage() + "\n");
process.exitCode = 2;
return { ...commandFailure("INVALID_USAGE"), project_root: projectRoot };
}

module.exports = {
usage,
governanceIndexCommand,
};
Loading
Loading