@deftai/directive 0.55.2 → 0.56.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { dispatch } from "../dispatch.js";
5
5
  import { runInit } from "../init-cli/init.js";
6
+ import { runMigrate } from "../init-cli/migrate.js";
6
7
  import { runUpdate } from "../init-cli/update.js";
7
8
  import { routeArgv } from "./route-argv.js";
8
9
  function defaultIo() {
@@ -32,6 +33,9 @@ export async function routeAndDispatch(argv, io) {
32
33
  if (first === "update") {
33
34
  return runUpdate(rest, io ?? defaultIo());
34
35
  }
36
+ if (first === "migrate") {
37
+ return runMigrate(rest, io ?? defaultIo());
38
+ }
35
39
  return dispatch(routed.argv, io);
36
40
  }
37
41
  //# sourceMappingURL=index.js.map
@@ -3,7 +3,7 @@
3
3
  * Mirrors the `task <namespace>:<verb>` surface one-to-one.
4
4
  */
5
5
  /** Top-level UX verbs promoted above the namespace layer (#1670). */
6
- export declare const TOP_LEVEL_UX_VERBS: readonly ["init", "update", "bootstrap", "check", "doctor", "version", "feature"];
6
+ export declare const TOP_LEVEL_UX_VERBS: readonly ["init", "update", "migrate", "bootstrap", "check", "doctor", "version", "feature"];
7
7
  /** Stubbed until a later story lands the handler (#1670 / #11). */
8
8
  export declare const STUBBED_TOP_LEVEL_VERBS: Set<string>;
9
9
  /** Registered but not yet implemented as TS handlers. */
@@ -7,6 +7,7 @@ import { resolveCanonicalVerb } from "../dispatch.js";
7
7
  export const TOP_LEVEL_UX_VERBS = [
8
8
  "init",
9
9
  "update",
10
+ "migrate",
10
11
  "bootstrap",
11
12
  "check",
12
13
  "doctor",
@@ -90,7 +91,7 @@ function routeTopLevel(first, rest) {
90
91
  if (first === "check" || first === "doctor") {
91
92
  return { kind: "dispatch", argv: [first, ...rest] };
92
93
  }
93
- if (first === "init" || first === "update") {
94
+ if (first === "init" || first === "update" || first === "migrate") {
94
95
  return { kind: "dispatch", argv: [first, ...rest] };
95
96
  }
96
97
  if (STUBBED_TOP_LEVEL_VERBS.has(first)) {
@@ -8,7 +8,7 @@ export interface DispatchIo {
8
8
  writeErr: (text: string) => void;
9
9
  }
10
10
  /** CLI modules in packages/cli/src (excluding parity harnesses and bin/index). */
11
- export declare const CLI_MODULE_VERBS: readonly ["cache", "check", "capacity-backfill", "capacity-show", "codebase-default-extractor", "codebase-map", "codebase-map-fresh", "codebase-projection-registry", "codebase-provider", "doctor", "parity", "policy", "pr-closing-keywords", "pr-merge-readiness", "pr-monitor", "pr-protected-issues", "pr-wait-mergeable", "preflight-cache", "preflight-gh", "probe-session", "release", "release-e2e", "release-publish", "release-rollback", "scope-lifecycle", "slice", "subagent-monitor", "toolchain-check", "triage-actions", "triage-bootstrap", "triage-bulk", "triage-classify", "triage-help", "triage-queue", "triage-reconcile", "triage-refresh", "triage-scope", "triage-scope-drift", "triage-smoketest", "triage-subscribe", "triage-summary", "triage-welcome", "ts-check-lane", "vbrief-activate", "vbrief-build", "vbrief-preflight", "vbrief-reconcile", "vbrief-validate", "vbrief-validation", "verify-branch", "verify-encoding", "verify-hooks-installed", "verify-investigation", "verify-judgment-gates", "verify-no-task-runtime", "validate-links", "validate-strategy-output", "verify-capacity", "verify-content-manifest", "verify-scm-boundary", "verify-session-ritual", "verify-stubs", "rule-ownership-lint", "verify-story-ready", "verify-tools", "verify-wip-cap"];
11
+ export declare const CLI_MODULE_VERBS: readonly ["cache", "check", "capacity-backfill", "capacity-show", "codebase-default-extractor", "codebase-map", "codebase-map-fresh", "codebase-projection-registry", "codebase-provider", "doctor", "parity", "policy", "pr-closing-keywords", "pr-merge-readiness", "pr-monitor", "pr-protected-issues", "pr-wait-mergeable", "preflight-cache", "preflight-gh", "probe-session", "release", "release-e2e", "release-publish", "release-rollback", "scope-lifecycle", "slice", "subagent-monitor", "toolchain-check", "triage-actions", "triage-bootstrap", "triage-bulk", "triage-classify", "triage-help", "triage-queue", "triage-reconcile", "triage-refresh", "triage-scope", "triage-scope-drift", "triage-smoketest", "triage-subscribe", "triage-summary", "triage-welcome", "ts-check-lane", "vbrief-activate", "vbrief-build", "vbrief-preflight", "vbrief-reconcile", "vbrief-validate", "vbrief-validation", "verify-branch", "verify-encoding", "verify-hooks-installed", "verify-investigation", "verify-judgment-gates", "verify-no-task-runtime", "validate-links", "validate-strategy-output", "verify-bridge-drift", "verify-capacity", "verify-content-manifest", "verify-go-freeze", "verify-scm-boundary", "verify-session-ritual", "verify-stubs", "rule-ownership-lint", "verify-story-ready", "verify-tools", "verify-wip-cap"];
12
12
  /** Core-only CLI entrypoints without a packages/cli wrapper. */
13
13
  export declare const CORE_MODULE_VERBS: readonly ["scm", "github-auth-modes", "github-body", "issue-emit", "issue-ingest", "reconcile-issues", "swarm-launch", "swarm-complete-cohort", "swarm-readiness", "swarm-routing-verify", "swarm-routing-set", "swarm-verify-review-clean", "swarm-worktrees", "framework-commands", "pack-render", "packs-slice", "prd-render", "project-render", "roadmap-render", "spec-render", "spec-validate", "code-structure-validate", "pack-migrate-skills", "pack-migrate-rules", "pack-migrate-strategies", "pack-migrate-patterns", "pack-migrate-swarm-spec", "policy-set", "scope-undo", "scope-demote", "scope-decompose", "changelog-resolve-unreleased", "architecture-preflight-sor"];
14
14
  /** Task-style aliases (framework_commands / Taskfile names). */
package/dist/dispatch.js CHANGED
@@ -74,8 +74,10 @@ export const CLI_MODULE_VERBS = [
74
74
  "verify-no-task-runtime",
75
75
  "validate-links",
76
76
  "validate-strategy-output",
77
+ "verify-bridge-drift",
77
78
  "verify-capacity",
78
79
  "verify-content-manifest",
80
+ "verify-go-freeze",
79
81
  "verify-scm-boundary",
80
82
  "verify-session-ritual",
81
83
  "verify-stubs",
@@ -140,6 +142,8 @@ export const VERB_ALIASES = {
140
142
  "verify:rule-ownership": "rule-ownership-lint",
141
143
  "rule:ownership-lint": "rule-ownership-lint",
142
144
  "verify:content-manifest": "verify-content-manifest",
145
+ "verify:go-freeze": "verify-go-freeze",
146
+ "verify:bridge-drift": "verify-bridge-drift",
143
147
  "verify:scm-boundary": "verify-scm-boundary",
144
148
  "verify:capacity": "verify-capacity",
145
149
  "verify:session-ritual": "verify-session-ritual",
@@ -170,6 +174,8 @@ const SUBDIR_CLI_STEMS = {
170
174
  "rule-ownership-lint": "verify-source-cli/rule-ownership-lint",
171
175
  "verify-content-manifest": "verify-source-cli/verify-content-manifest",
172
176
  "verify-scm-boundary": "verify-source-cli/verify-scm-boundary",
177
+ "verify-go-freeze": "gates-cli/verify-go-freeze",
178
+ "verify-bridge-drift": "gates-cli/verify-bridge-drift",
173
179
  "validate-links": "content-validate-cli/validate-links",
174
180
  "verify-capacity": "content-validate-cli/verify-capacity",
175
181
  "validate-strategy-output": "content-validate-cli/validate-strategy-output",
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ interface ParsedArgs {
3
+ projectRoot: string | null;
4
+ error?: string;
5
+ }
6
+ /** Parse verify-bridge-drift CLI args (#1912). */
7
+ export declare function parseArgs(argv: string[]): ParsedArgs;
8
+ /** Run the cross-surface drift gate and return the process exit code (three-state). */
9
+ export declare function run(argv: string[]): number;
10
+ export {};
11
+ //# sourceMappingURL=verify-bridge-drift.d.ts.map
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { evaluateBridgeDrift } from "@deftai/directive-core/legacy-bridge";
5
+ /** Parse verify-bridge-drift CLI args (#1912). */
6
+ export function parseArgs(argv) {
7
+ const parsed = { projectRoot: null };
8
+ for (let i = 0; i < argv.length; i += 1) {
9
+ const arg = argv[i];
10
+ if (arg === "--project-root") {
11
+ const value = argv[i + 1];
12
+ if (value === undefined) {
13
+ return { ...parsed, error: "argument --project-root: expected one argument" };
14
+ }
15
+ parsed.projectRoot = value;
16
+ i += 1;
17
+ }
18
+ else if (arg?.startsWith("--project-root=")) {
19
+ parsed.projectRoot = arg.slice("--project-root=".length);
20
+ }
21
+ else {
22
+ return { ...parsed, error: `unrecognized argument: ${arg}` };
23
+ }
24
+ }
25
+ return parsed;
26
+ }
27
+ /** Run the cross-surface drift gate and return the process exit code (three-state). */
28
+ export function run(argv) {
29
+ const args = parseArgs(argv);
30
+ if (args.error !== undefined) {
31
+ process.stderr.write(`verify_bridge_drift: ${args.error}\n`);
32
+ return 2;
33
+ }
34
+ const root = resolve(args.projectRoot ?? ".");
35
+ const result = evaluateBridgeDrift(root);
36
+ if (result.stream === "stdout") {
37
+ process.stdout.write(`${result.message}\n`);
38
+ }
39
+ else {
40
+ process.stderr.write(`${result.message}\n`);
41
+ }
42
+ return result.code;
43
+ }
44
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
45
+ process.exit(run(process.argv.slice(2)));
46
+ }
47
+ //# sourceMappingURL=verify-bridge-drift.js.map
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ interface ParsedArgs {
3
+ projectRoot: string | null;
4
+ error?: string;
5
+ }
6
+ /** Parse verify-go-freeze CLI args (#1912). */
7
+ export declare function parseArgs(argv: string[]): ParsedArgs;
8
+ /** Run the freeze gate and return the process exit code (three-state). */
9
+ export declare function run(argv: string[]): number;
10
+ export {};
11
+ //# sourceMappingURL=verify-go-freeze.d.ts.map
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { evaluateGoFreeze, FREEZE_BYPASS_ENV } from "@deftai/directive-core/legacy-bridge";
5
+ /** Parse verify-go-freeze CLI args (#1912). */
6
+ export function parseArgs(argv) {
7
+ const parsed = { projectRoot: null };
8
+ for (let i = 0; i < argv.length; i += 1) {
9
+ const arg = argv[i];
10
+ if (arg === "--project-root") {
11
+ const value = argv[i + 1];
12
+ if (value === undefined) {
13
+ return { ...parsed, error: "argument --project-root: expected one argument" };
14
+ }
15
+ parsed.projectRoot = value;
16
+ i += 1;
17
+ }
18
+ else if (arg?.startsWith("--project-root=")) {
19
+ parsed.projectRoot = arg.slice("--project-root=".length);
20
+ }
21
+ else {
22
+ return { ...parsed, error: `unrecognized argument: ${arg}` };
23
+ }
24
+ }
25
+ return parsed;
26
+ }
27
+ /** Run the freeze gate and return the process exit code (three-state). */
28
+ export function run(argv) {
29
+ const args = parseArgs(argv);
30
+ if (args.error !== undefined) {
31
+ process.stderr.write(`verify_go_freeze: ${args.error}\n`);
32
+ return 2;
33
+ }
34
+ const root = resolve(args.projectRoot ?? ".");
35
+ const allowBump = process.env[FREEZE_BYPASS_ENV] === "1";
36
+ const result = evaluateGoFreeze(root, { allowBump });
37
+ if (result.stream === "stdout") {
38
+ process.stdout.write(`${result.message}\n`);
39
+ }
40
+ else {
41
+ process.stderr.write(`${result.message}\n`);
42
+ }
43
+ return result.code;
44
+ }
45
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
46
+ process.exit(run(process.argv.slice(2)));
47
+ }
48
+ //# sourceMappingURL=verify-go-freeze.js.map
@@ -2,4 +2,6 @@
2
2
  export declare const CANONICAL_INIT_ARGV: readonly ["--yes", "--repo-root", ".", "--json"];
3
3
  /** Canonical headless upgrade argv (#1339 / #1409). */
4
4
  export declare const CANONICAL_UPDATE_ARGV: readonly ["--yes", "--upgrade", "--repo-root", ".", "--json"];
5
+ /** Canonical migrate argv: defaults to cwd, human-readable unless --json (#1941). */
6
+ export declare const CANONICAL_MIGRATE_ARGV: readonly ["--repo-root", "."];
5
7
  //# sourceMappingURL=constants.d.ts.map
@@ -2,4 +2,6 @@
2
2
  export const CANONICAL_INIT_ARGV = ["--yes", "--repo-root", ".", "--json"];
3
3
  /** Canonical headless upgrade argv (#1339 / #1409). */
4
4
  export const CANONICAL_UPDATE_ARGV = ["--yes", "--upgrade", "--repo-root", ".", "--json"];
5
+ /** Canonical migrate argv: defaults to cwd, human-readable unless --json (#1941). */
6
+ export const CANONICAL_MIGRATE_ARGV = ["--repo-root", "."];
5
7
  //# sourceMappingURL=constants.js.map
@@ -1,3 +1,3 @@
1
1
  import type { DispatchIo } from "../dispatch.js";
2
- export declare function runInit(argv: readonly string[], io: DispatchIo): number;
2
+ export declare function runInit(argv: readonly string[], io: DispatchIo): Promise<number>;
3
3
  //# sourceMappingURL=init.d.ts.map
@@ -1,11 +1,11 @@
1
+ import { parseInitArgv, runInitDepositCli } from "@deftai/directive-core/init-deposit";
1
2
  import { CANONICAL_INIT_ARGV } from "./constants.js";
2
- import { runDeftInstall } from "./run-deft-install.js";
3
3
  export function runInit(argv, io) {
4
- return runDeftInstall({
5
- verb: "init",
6
- canonicalArgv: CANONICAL_INIT_ARGV,
7
- userArgv: argv,
8
- io,
4
+ const args = parseInitArgv(CANONICAL_INIT_ARGV, argv);
5
+ return runInitDepositCli({
6
+ ...args,
7
+ writeOut: io.writeOut,
8
+ writeErr: io.writeErr,
9
9
  });
10
10
  }
11
11
  //# sourceMappingURL=init.js.map
@@ -0,0 +1,3 @@
1
+ import type { DispatchIo } from "../dispatch.js";
2
+ export declare function runMigrate(argv: readonly string[], io: DispatchIo): number;
3
+ //# sourceMappingURL=migrate.d.ts.map
@@ -0,0 +1,29 @@
1
+ import { parseInitArgv, runMigrateCli } from "@deftai/directive-core/init-deposit";
2
+ import { CANONICAL_MIGRATE_ARGV } from "./constants.js";
3
+ /**
4
+ * `directive migrate` (alias `deft migrate`) -- stage-2 provenance verb (#1941):
5
+ * stamp a canonical-vendored `.deft/core` deposit as npm-managed. Thin wrapper
6
+ * over the core orchestrator; maps the three-state result to a 0/1/2 exit code.
7
+ */
8
+ const NO_EFFECT_CONFIRMATION_FLAGS = new Set([
9
+ "--yes",
10
+ "--non-interactive",
11
+ "/yes",
12
+ "/non-interactive",
13
+ ]);
14
+ export function runMigrate(argv, io) {
15
+ const args = parseInitArgv(CANONICAL_MIGRATE_ARGV, argv);
16
+ // `migrate` reuses `parseInitArgv`, which understands the init/update headless
17
+ // confirmation flags. migrate has no interactive prompts, so acknowledge the
18
+ // flag rather than silently swallowing it (a natural reflex from init/update).
19
+ if (argv.some((arg) => NO_EFFECT_CONFIRMATION_FLAGS.has(arg))) {
20
+ io.writeErr("directive migrate: --yes/--non-interactive has no effect (migrate runs non-interactively and never prompts).\n");
21
+ }
22
+ return runMigrateCli({
23
+ projectDir: args.projectDir,
24
+ jsonOut: args.jsonOut,
25
+ writeOut: io.writeOut,
26
+ writeErr: io.writeErr,
27
+ });
28
+ }
29
+ //# sourceMappingURL=migrate.js.map
@@ -1,3 +1,3 @@
1
1
  import type { DispatchIo } from "../dispatch.js";
2
- export declare function runUpdate(argv: readonly string[], io: DispatchIo): number;
2
+ export declare function runUpdate(argv: readonly string[], io: DispatchIo): Promise<number>;
3
3
  //# sourceMappingURL=update.d.ts.map
@@ -1,11 +1,11 @@
1
+ import { parseUpdateArgv, runRefreshDepositCli } from "@deftai/directive-core/init-deposit";
1
2
  import { CANONICAL_UPDATE_ARGV } from "./constants.js";
2
- import { runDeftInstall } from "./run-deft-install.js";
3
3
  export function runUpdate(argv, io) {
4
- return runDeftInstall({
5
- verb: "update",
6
- canonicalArgv: CANONICAL_UPDATE_ARGV,
7
- userArgv: argv,
8
- io,
4
+ const args = parseUpdateArgv(CANONICAL_UPDATE_ARGV, argv);
5
+ return runRefreshDepositCli({
6
+ ...args,
7
+ writeOut: io.writeOut,
8
+ writeErr: io.writeErr,
9
9
  });
10
10
  }
11
11
  //# sourceMappingURL=update.js.map
@@ -7,6 +7,7 @@ export interface CommandCapture {
7
7
  export interface ParityCase {
8
8
  readonly name: string;
9
9
  readonly argv: readonly string[];
10
+ readonly seed?: string;
10
11
  }
11
12
  export interface ParityDiff {
12
13
  readonly caseName: string;
@@ -22,6 +23,8 @@ export interface ParityResult {
22
23
  }
23
24
  /** Strip volatile UUIDs and timestamps before compare. */
24
25
  export declare function normalizeOutput(text: string): string;
26
+ /** Keep only operator-facing triage stderr; drop uv/tooling noise from Python spawns. */
27
+ export declare function normalizeStderr(text: string): string;
25
28
  /** Build a throwaway project root with an empty audit-log parent directory. */
26
29
  export declare function buildFixtureRepo(): string;
27
30
  /** Diff one parity case between Python oracle and TS CLI. */
@@ -11,6 +11,7 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
11
11
  import { tmpdir } from "node:os";
12
12
  import { dirname, join, resolve } from "node:path";
13
13
  import { fileURLToPath } from "node:url";
14
+ import { cachePut } from "../../core/dist/cache/operations.js";
14
15
  /** Strip volatile UUIDs and timestamps before compare. */
15
16
  export function normalizeOutput(text) {
16
17
  return text
@@ -18,10 +19,26 @@ export function normalizeOutput(text) {
18
19
  .replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/g, "<TS>")
19
20
  .replace(/Using CPython[^\n]*\n/g, "")
20
21
  .replace(/Creating virtual environment[^\n]*\n/g, "")
21
- .replace(/Installed \d+ packages[^\n]*\n/g, "");
22
+ .replace(/Installed \d+ packages[^\n]*\n/g, "")
23
+ .replace(/Downloading[^\n]*\n/g, "")
24
+ .replace(/Built[^\n]*\n/g, "")
25
+ .replace(/triage_actions: needs-ac comment not posted[^\n]*/g, "triage_actions: needs-ac comment not posted <GH-FAIL>");
26
+ }
27
+ /** Keep only operator-facing triage stderr; drop uv/tooling noise from Python spawns. */
28
+ export function normalizeStderr(text) {
29
+ return normalizeOutput(text)
30
+ .split("\n")
31
+ .filter((line) => line.length === 0 || line.startsWith("triage_actions:"))
32
+ .join("\n");
22
33
  }
23
34
  function runCapture(cmd, args, cwd, env = {}) {
24
- const merged = { ...process.env, ...env };
35
+ const merged = {
36
+ ...process.env,
37
+ ...env,
38
+ GITHUB_TOKEN: "",
39
+ GH_TOKEN: "",
40
+ GH_ENTERPRISE_TOKEN: "",
41
+ };
25
42
  for (const key of Object.keys(merged)) {
26
43
  if (merged[key] === undefined)
27
44
  delete merged[key];
@@ -59,13 +76,19 @@ function pythonWrapperScript(deftRoot, fixtureRoot) {
59
76
  "sys.path.insert(0, str(deft_root / 'scripts'))",
60
77
  "import candidates_log as cl",
61
78
  "cl.DEFAULT_LOG_PATH = fixture / 'vbrief/.eval/candidates.jsonl'",
79
+ "import cache as cache_mod",
80
+ "cache_mod.DEFAULT_CACHE_ROOT = fixture / '.deft-cache'",
62
81
  "import triage_actions",
63
82
  "triage_actions.candidates_log = cl",
83
+ "triage_actions.cache = cache_mod",
84
+ "def _parity_gh(args):",
85
+ " raise triage_actions.UpstreamCloseError('gh disabled for parity')",
86
+ "triage_actions._run_gh = _parity_gh",
64
87
  "raise SystemExit(triage_actions.main(sys.argv[1:]))",
65
88
  ].join("\n");
66
89
  }
67
90
  function runPythonTriageAction(deftRoot, fixtureRoot, argv) {
68
- const cap = runCapture("uv", ["run", "python", "-c", pythonWrapperScript(deftRoot, fixtureRoot), ...argv], deftRoot, { TRIAGE_PARITY_FIXTURE: fixtureRoot });
91
+ const cap = runCapture("uv", ["run", "python", "-c", pythonWrapperScript(deftRoot, fixtureRoot), ...argv], deftRoot, { TRIAGE_PARITY_FIXTURE: fixtureRoot, DEFT_TRIAGE_ACTIONS_PARITY: "1" });
69
92
  return { exitCode: cap.status, stdout: cap.stdout, stderr: cap.stderr };
70
93
  }
71
94
  function runTsTriageAction(deftRoot, fixtureRoot, argv) {
@@ -74,15 +97,15 @@ function runTsTriageAction(deftRoot, fixtureRoot, argv) {
74
97
  ...argv,
75
98
  "--project-root",
76
99
  fixtureRoot,
77
- ], deftRoot);
100
+ ], deftRoot, { DEFT_TRIAGE_ACTIONS_PARITY: "1" });
78
101
  return { exitCode: cap.status, stdout: cap.stdout, stderr: cap.stderr };
79
102
  }
80
103
  /** Diff one parity case between Python oracle and TS CLI. */
81
104
  export function diffCase(python, ts, caseName) {
82
105
  const pyOut = normalizeOutput(python.stdout);
83
106
  const tsOut = normalizeOutput(ts.stdout);
84
- const pyErr = normalizeOutput(python.stderr);
85
- const tsErr = normalizeOutput(ts.stderr);
107
+ const pyErr = normalizeStderr(python.stderr);
108
+ const tsErr = normalizeStderr(ts.stderr);
86
109
  return {
87
110
  caseName,
88
111
  exitMismatch: python.exitCode !== ts.exitCode,
@@ -129,6 +152,71 @@ export const PARITY_CASES = [
129
152
  name: "accept-idempotent",
130
153
  argv: ["accept", "--issue", "9", "--repo", "deftai/directive", "--actor", "agent:test"],
131
154
  },
155
+ {
156
+ name: "needs-ac-default",
157
+ argv: ["needs-ac", "--issue", "10", "--repo", "deftai/directive", "--actor", "agent:test"],
158
+ },
159
+ {
160
+ name: "status-empty",
161
+ argv: ["status", "--issue", "11", "--repo", "deftai/directive"],
162
+ },
163
+ {
164
+ name: "status-with-defer",
165
+ argv: ["status", "--issue", "12", "--repo", "deftai/directive"],
166
+ seed: "defer-status",
167
+ },
168
+ {
169
+ name: "reset-no-prior",
170
+ argv: ["reset", "--issue", "13", "--repo", "deftai/directive"],
171
+ },
172
+ {
173
+ name: "reset-success",
174
+ argv: ["reset", "--issue", "14", "--repo", "deftai/directive", "--actor", "agent:test"],
175
+ seed: "defer-reset",
176
+ },
177
+ {
178
+ name: "reset-idempotent",
179
+ argv: ["reset", "--issue", "15", "--repo", "deftai/directive"],
180
+ seed: "reset-idempotent",
181
+ },
182
+ {
183
+ name: "history-empty",
184
+ argv: ["history", "--issue", "16", "--repo", "deftai/directive"],
185
+ },
186
+ {
187
+ name: "history-multi",
188
+ argv: ["history", "--issue", "17", "--repo", "deftai/directive"],
189
+ seed: "history-multi",
190
+ },
191
+ {
192
+ name: "mark-duplicate-missing-cache",
193
+ argv: ["mark-duplicate", "--issue", "18", "--repo", "deftai/directive", "--of", "99"],
194
+ },
195
+ {
196
+ name: "mark-duplicate-success",
197
+ argv: [
198
+ "mark-duplicate",
199
+ "--issue",
200
+ "19",
201
+ "--repo",
202
+ "deftai/directive",
203
+ "--of",
204
+ "20",
205
+ "--actor",
206
+ "agent:test",
207
+ ],
208
+ seed: "mark-duplicate-cache",
209
+ },
210
+ {
211
+ name: "mark-duplicate-idempotent",
212
+ argv: ["mark-duplicate", "--issue", "21", "--repo", "deftai/directive", "--of", "22"],
213
+ seed: "mark-duplicate-idempotent",
214
+ },
215
+ {
216
+ name: "mark-duplicate-self",
217
+ argv: ["mark-duplicate", "--issue", "23", "--repo", "deftai/directive", "--of", "23"],
218
+ seed: "mark-duplicate-cache-self",
219
+ },
132
220
  ];
133
221
  function seedAcceptFixture(fixtureRoot) {
134
222
  const entry = {
@@ -141,6 +229,69 @@ function seedAcceptFixture(fixtureRoot) {
141
229
  };
142
230
  writeFileSync(join(fixtureRoot, "vbrief/.eval/candidates.jsonl"), `${JSON.stringify(entry, Object.keys(entry).sort())}\n`, "utf8");
143
231
  }
232
+ function seedAuditEntry(fixtureRoot, issueNumber, decision, extras = {}) {
233
+ const entry = {
234
+ actor: "agent:test",
235
+ decision,
236
+ decision_id: "11111111-1111-1111-1111-111111111111",
237
+ issue_number: issueNumber,
238
+ repo: "deftai/directive",
239
+ timestamp: "2026-06-18T12:00:00Z",
240
+ ...extras,
241
+ };
242
+ const path = join(fixtureRoot, "vbrief/.eval/candidates.jsonl");
243
+ const line = `${JSON.stringify(entry, Object.keys(entry).sort())}\n`;
244
+ writeFileSync(path, line, { encoding: "utf8", flag: "a" });
245
+ }
246
+ function seedCacheIssue(fixtureRoot, issueNumber) {
247
+ const cacheRoot = join(fixtureRoot, ".deft-cache");
248
+ cachePut("github-issue", `deftai/directive/${issueNumber}`, { number: issueNumber, title: "parity fixture", body: "fixture body" }, { cacheRoot });
249
+ }
250
+ function seedFixture(fixtureRoot, seed) {
251
+ if (seed === undefined)
252
+ return;
253
+ switch (seed) {
254
+ case "defer-status":
255
+ seedAuditEntry(fixtureRoot, 12, "defer", { reason: "later" });
256
+ break;
257
+ case "defer-reset":
258
+ seedAuditEntry(fixtureRoot, 14, "defer", { reason: "later" });
259
+ break;
260
+ case "reset-idempotent":
261
+ seedAuditEntry(fixtureRoot, 15, "reset", {
262
+ decision_id: "22222222-2222-2222-2222-222222222222",
263
+ prior_decision_id: "11111111-1111-1111-1111-111111111111",
264
+ });
265
+ break;
266
+ case "history-multi":
267
+ seedAuditEntry(fixtureRoot, 17, "defer", {
268
+ decision_id: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
269
+ reason: "first",
270
+ timestamp: "2026-06-18T10:00:00Z",
271
+ });
272
+ seedAuditEntry(fixtureRoot, 17, "needs-ac", {
273
+ decision_id: "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
274
+ reason: "needs criteria",
275
+ timestamp: "2026-06-18T11:00:00Z",
276
+ });
277
+ break;
278
+ case "mark-duplicate-cache":
279
+ seedCacheIssue(fixtureRoot, 20);
280
+ break;
281
+ case "mark-duplicate-idempotent":
282
+ seedCacheIssue(fixtureRoot, 22);
283
+ seedAuditEntry(fixtureRoot, 21, "mark-duplicate", {
284
+ decision_id: "cccccccc-cccc-cccc-cccc-cccccccccccc",
285
+ linked_to: 22,
286
+ });
287
+ break;
288
+ case "mark-duplicate-cache-self":
289
+ seedCacheIssue(fixtureRoot, 23);
290
+ break;
291
+ default:
292
+ break;
293
+ }
294
+ }
144
295
  /** Run all parity cases; returns aggregate result. */
145
296
  export function runParity() {
146
297
  const deftRoot = resolveDeftRoot();
@@ -152,6 +303,10 @@ export function runParity() {
152
303
  seedAcceptFixture(pyFixture);
153
304
  seedAcceptFixture(tsFixture);
154
305
  }
306
+ else {
307
+ seedFixture(pyFixture, testCase.seed);
308
+ seedFixture(tsFixture, testCase.seed);
309
+ }
155
310
  try {
156
311
  const python = runPythonTriageAction(deftRoot, pyFixture, testCase.argv);
157
312
  const ts = runTsTriageAction(deftRoot, tsFixture, testCase.argv);
@@ -6,6 +6,8 @@ interface ParsedArgs {
6
6
  reason?: string;
7
7
  resumeOn?: string;
8
8
  actor?: string;
9
+ comment?: string;
10
+ ofN?: number;
9
11
  projectRoot: string;
10
12
  error?: string;
11
13
  }
@@ -1,7 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import { resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { accept, createDefaultDeps, deferAction, reject, TriageError, UpstreamCloseError, } from "../../core/dist/triage/actions/index.js";
4
+ import { accept, createDefaultDeps, deferAction, formatDecision, history, markDuplicate, needsAc, reject, reset, status, TriageError, UpstreamCloseError, } from "../../core/dist/triage/actions/index.js";
5
+ const VALID_CMDS = new Set([
6
+ "accept",
7
+ "reject",
8
+ "defer",
9
+ "needs-ac",
10
+ "mark-duplicate",
11
+ "status",
12
+ "reset",
13
+ "history",
14
+ ]);
5
15
  /** Parse triage-actions CLI argv mirroring ``triage_actions.py`` argparse. */
6
16
  export function parseArgs(argv) {
7
17
  const parsed = { projectRoot: process.cwd() };
@@ -61,6 +71,26 @@ export function parseArgs(argv) {
61
71
  else if (arg?.startsWith("--actor=")) {
62
72
  parsed.actor = arg.slice("--actor=".length);
63
73
  }
74
+ else if (arg === "--comment") {
75
+ const value = argv[i + 1];
76
+ if (value === undefined)
77
+ return { ...parsed, error: "argument --comment: expected one argument" };
78
+ parsed.comment = value;
79
+ i += 1;
80
+ }
81
+ else if (arg?.startsWith("--comment=")) {
82
+ parsed.comment = arg.slice("--comment=".length);
83
+ }
84
+ else if (arg === "--of") {
85
+ const value = argv[i + 1];
86
+ if (value === undefined)
87
+ return { ...parsed, error: "argument --of: expected one argument" };
88
+ parsed.ofN = Number.parseInt(value, 10);
89
+ i += 1;
90
+ }
91
+ else if (arg?.startsWith("--of=")) {
92
+ parsed.ofN = Number.parseInt(arg.slice("--of=".length), 10);
93
+ }
64
94
  else if (arg === "--project-root") {
65
95
  const value = argv[i + 1];
66
96
  if (value === undefined)
@@ -84,7 +114,7 @@ export function run(argv) {
84
114
  process.stderr.write(`triage_actions: ${args.error}\n`);
85
115
  return 2;
86
116
  }
87
- if (args.cmd !== "accept" && args.cmd !== "reject" && args.cmd !== "defer") {
117
+ if (!VALID_CMDS.has(args.cmd ?? "")) {
88
118
  process.stderr.write(`triage_actions: unknown subcommand ${args.cmd ?? ""}\n`);
89
119
  return 2;
90
120
  }
@@ -104,6 +134,10 @@ export function run(argv) {
104
134
  process.stderr.write("triage_actions: argument --reason: expected one argument\n");
105
135
  return 2;
106
136
  }
137
+ if (args.cmd === "mark-duplicate" && (args.ofN === undefined || Number.isNaN(args.ofN))) {
138
+ process.stderr.write("triage_actions: argument --of: expected one argument\n");
139
+ return 2;
140
+ }
107
141
  const projectRoot = resolve(args.projectRoot);
108
142
  const deps = createDefaultDeps(projectRoot);
109
143
  const n = args.issue;
@@ -120,7 +154,7 @@ export function run(argv) {
120
154
  });
121
155
  process.stdout.write(`reject #${n} (${repo}) -> ${decisionId}\n`);
122
156
  }
123
- else {
157
+ else if (args.cmd === "defer") {
124
158
  const decisionId = deferAction(n, repo, args.reason, deps, {
125
159
  actor: args.actor,
126
160
  resumeOn: args.resumeOn,
@@ -128,6 +162,40 @@ export function run(argv) {
128
162
  });
129
163
  process.stdout.write(`defer #${n} (${repo}) -> ${decisionId}\n`);
130
164
  }
165
+ else if (args.cmd === "needs-ac") {
166
+ const decisionId = needsAc(n, repo, deps, {
167
+ actor: args.actor,
168
+ comment: args.comment,
169
+ projectRoot,
170
+ });
171
+ process.stdout.write(`needs-ac #${n} (${repo}) -> ${decisionId}\n`);
172
+ }
173
+ else if (args.cmd === "mark-duplicate") {
174
+ const ofN = args.ofN;
175
+ const decisionId = markDuplicate(n, repo, ofN, deps, {
176
+ actor: args.actor,
177
+ projectRoot,
178
+ });
179
+ process.stdout.write(`mark-duplicate #${n} -> #${ofN} (${repo}) -> ${decisionId}\n`);
180
+ }
181
+ else if (args.cmd === "status") {
182
+ process.stdout.write(`${formatDecision(status(n, repo, deps, { projectRoot }))}\n`);
183
+ }
184
+ else if (args.cmd === "reset") {
185
+ const decisionId = reset(n, repo, deps, { actor: args.actor, projectRoot });
186
+ process.stdout.write(`reset #${n} (${repo}) -> ${decisionId}\n`);
187
+ }
188
+ else {
189
+ const entries = history(n, repo, deps, { projectRoot });
190
+ if (entries.length === 0) {
191
+ process.stdout.write(`${formatDecision(null)}\n`);
192
+ }
193
+ else {
194
+ for (const entry of entries) {
195
+ process.stdout.write(`${formatDecision(entry)}\n`);
196
+ }
197
+ }
198
+ }
131
199
  }
132
200
  catch (err) {
133
201
  if (err instanceof TriageError || err instanceof UpstreamCloseError) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive",
3
- "version": "0.55.2",
3
+ "version": "0.56.1",
4
4
  "description": "Directive CLI — npm install path for the Deft Directive framework.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -42,7 +42,8 @@
42
42
  "provenance": true
43
43
  },
44
44
  "dependencies": {
45
- "@deftai/directive-core": "^0.55.2"
45
+ "@deftai/directive-core": "^0.56.1",
46
+ "@deftai/directive-content": "^0.56.1"
46
47
  },
47
48
  "scripts": {
48
49
  "build": "tsc -b"