@deftai/directive 0.68.1 → 0.69.0

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.
@@ -147,7 +147,11 @@ export declare function runPolicySet(argv: string[], io: DispatchIo): number;
147
147
  export declare function resolveCanonicalVerb(verb: string): string | null;
148
148
  /** Sorted list of all registered verb names (canonical + aliases). */
149
149
  export declare function registeredVerbs(): readonly string[];
150
- /** Print dispatcher help listing every registered verb. */
150
+ /**
151
+ * Print dispatcher help. Leads with the three-command model (init / update /
152
+ * doctor) + first-run guidance so a no-arg `directive` orients a newcomer before
153
+ * the exhaustive verb list, which stays available below for power users (#2273).
154
+ */
151
155
  export declare function printHelp(io?: DispatchIo): void;
152
156
  /** Dispatch argv to a registered verb; returns the handler exit code. */
153
157
  export declare function dispatch(argv: string[], io?: DispatchIo): Promise<number>;
package/dist/dispatch.js CHANGED
@@ -7,8 +7,8 @@ import { createHash } from "node:crypto";
7
7
  import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, readSync, rmSync, statSync, writeFileSync, } from "node:fs";
8
8
  import { homedir, tmpdir } from "node:os";
9
9
  import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
10
- import { engineInfo } from "@deftai/directive-core";
11
- import { parseInitArgv, runInitDepositCli, userConfigDir, } from "@deftai/directive-core/init-deposit";
10
+ import { engineInfo, userConfig } from "@deftai/directive-core";
11
+ import { parseInitArgv, runInitDepositCli } from "@deftai/directive-core/init-deposit";
12
12
  import { appendAuditLog, disclosureLine, migrateLegacyPolicyKey, PLAN_POLICY_KEY, projectDefinitionPath, resolvePolicy, resolveWipCap, setPolicy, } from "@deftai/directive-core/policy";
13
13
  import { defaultWhich } from "@deftai/directive-core/scm";
14
14
  import { KNOWN_SUBAGENT_BACKEND_IDS, probeSubagentBackends, resolveSwarmSubagentBackend, } from "@deftai/directive-core/swarm";
@@ -1501,16 +1501,20 @@ function bootstrapPhaseLabel(phase) {
1501
1501
  return "project";
1502
1502
  return "spec";
1503
1503
  }
1504
- function resolveBootstrapUserMdPath() {
1505
- const override = process.env.DEFT_USER_PATH?.trim();
1506
- if (override)
1507
- return resolve(override);
1508
- return join(userConfigDir(), "USER.md");
1504
+ /**
1505
+ * Bootstrap USER.md path resolver (#2271). Delegates to the shared first-hit-
1506
+ * wins resolver so the CLI bootstrap, session-start, and doctor share one
1507
+ * source of truth. DEFT_USER_PATH precedence is preserved by the shared
1508
+ * resolver (rung 1). `projectRoot` scopes the workspace-local rung so a bridged
1509
+ * `<projectRoot>/.deft/USER.md` resolves without a manual DEFT_USER_PATH.
1510
+ */
1511
+ function resolveBootstrapUserMdPath(projectRoot) {
1512
+ return userConfig.resolveUserMdPath(projectRoot !== undefined ? { projectRoot } : {}).path;
1509
1513
  }
1510
1514
  function defaultBootstrapDeps() {
1511
1515
  return {
1512
1516
  deftCorePresent: (projectRoot) => isDirSafe(join(resolve(projectRoot), ".deft", "core")),
1513
- userMdPresent: () => isFileSafe(resolveBootstrapUserMdPath()),
1517
+ userMdPresent: (projectRoot) => isFileSafe(resolveBootstrapUserMdPath(projectRoot)),
1514
1518
  projectDefPresent: (projectRoot) => isFileSafe(projectDefinitionPath(projectRoot)),
1515
1519
  runInitDeposit: async (projectRoot, io) => {
1516
1520
  const initArgs = parseInitArgv(["--yes", "--repo-root", projectRoot, "--json"], []);
@@ -2323,9 +2327,29 @@ export function registeredVerbs() {
2323
2327
  ]);
2324
2328
  return [...names].sort();
2325
2329
  }
2326
- /** Print dispatcher help listing every registered verb. */
2330
+ /**
2331
+ * Print dispatcher help. Leads with the three-command model (init / update /
2332
+ * doctor) + first-run guidance so a no-arg `directive` orients a newcomer before
2333
+ * the exhaustive verb list, which stays available below for power users (#2273).
2334
+ */
2327
2335
  export function printHelp(io = defaultIo()) {
2328
- io.writeOut("Usage: directive <verb> [args...]\n\nRegistered verbs:\n");
2336
+ io.writeOut("directive -- the Deft Directive CLI\n" +
2337
+ "\n" +
2338
+ "Start here (most projects only need these three):\n" +
2339
+ " directive init Set up Directive in the current project (first-time setup)\n" +
2340
+ " directive update Refresh an existing install and self-heal the engine\n" +
2341
+ " directive doctor Diagnose the install and print the one next step\n" +
2342
+ "\n" +
2343
+ "First run? From the project root:\n" +
2344
+ " 1. npm i -g @deftai/directive (Node >= 20)\n" +
2345
+ " (pnpm: pnpm add -g @deftai/directive -- ensure PNPM_HOME is on PATH, run `pnpm setup` if needed)\n" +
2346
+ " 2. directive init\n" +
2347
+ " 3. directive doctor\n" +
2348
+ "New clone where `directive` will not run? Read the Cold-start bootstrap block at the top of README.md.\n" +
2349
+ "\n" +
2350
+ "Usage: directive <verb> [args...]\n" +
2351
+ "\n" +
2352
+ "Registered verbs:\n");
2329
2353
  for (const name of registeredVerbs()) {
2330
2354
  io.writeOut(` ${name}\n`);
2331
2355
  }
@@ -2,6 +2,41 @@
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
+ /**
6
+ * Flags that make `directive update` classify + print its plan without executing
7
+ * the refresh (#2266). Recognised in the USER argv (never part of the canonical
8
+ * always-applied argv above, which must stay a real refresh).
9
+ */
10
+ export declare const UPDATE_DRY_RUN_FLAGS: readonly ["--dry-run", "--plan"];
11
+ /**
12
+ * Flags that make `directive init` classify + print the dispatch plan without
13
+ * executing any deposit/refresh/migrate (#2265). Mirrors {@link UPDATE_DRY_RUN_FLAGS};
14
+ * recognised in the USER argv, never part of the canonical always-applied argv
15
+ * above (which must stay a real adoption dispatch).
16
+ */
17
+ export declare const INIT_DRY_RUN_FLAGS: readonly ["--dry-run", "--plan"];
18
+ /**
19
+ * Flags that switch `directive init` into headless manifest-emit mode (#2268):
20
+ * serialise the merged keystone `plan()` into a `{ version, files }` manifest
21
+ * with ALL execution side effects suppressed. Recognised in the USER argv; NOT
22
+ * part of the canonical always-applied argv above (which must stay a real,
23
+ * executing adoption dispatch — a headless flag there would silently suppress
24
+ * every install's side effects).
25
+ */
26
+ export declare const INIT_HEADLESS_FLAGS: readonly ["--headless", "/headless"];
27
+ /**
28
+ * Flag naming the manifest output target for `--headless` (#2268). Accepts the
29
+ * `--output=<path>` and `--output <path>` forms; when absent the manifest is
30
+ * written to stdout. Writing this file is the ONLY filesystem side effect the
31
+ * headless path is permitted to perform.
32
+ */
33
+ export declare const INIT_OUTPUT_FLAGS: readonly ["--output", "/output"];
5
34
  /** Canonical migrate argv: defaults to cwd, human-readable unless --json (#1941). */
6
35
  export declare const CANONICAL_MIGRATE_ARGV: readonly ["--repo-root", "."];
36
+ /**
37
+ * Subcommand flag selecting the vendored→hybrid `.deft/core` un-commit (#2269).
38
+ * `migrate --untrack-core` dispatches to the destructive un-track path; bare
39
+ * `migrate` keeps its non-destructive provenance-stamp behavior.
40
+ */
41
+ export declare const MIGRATE_UNTRACK_CORE_FLAG = "--untrack-core";
7
42
  //# sourceMappingURL=constants.d.ts.map
@@ -2,6 +2,41 @@
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
+ /**
6
+ * Flags that make `directive update` classify + print its plan without executing
7
+ * the refresh (#2266). Recognised in the USER argv (never part of the canonical
8
+ * always-applied argv above, which must stay a real refresh).
9
+ */
10
+ export const UPDATE_DRY_RUN_FLAGS = ["--dry-run", "--plan"];
11
+ /**
12
+ * Flags that make `directive init` classify + print the dispatch plan without
13
+ * executing any deposit/refresh/migrate (#2265). Mirrors {@link UPDATE_DRY_RUN_FLAGS};
14
+ * recognised in the USER argv, never part of the canonical always-applied argv
15
+ * above (which must stay a real adoption dispatch).
16
+ */
17
+ export const INIT_DRY_RUN_FLAGS = ["--dry-run", "--plan"];
18
+ /**
19
+ * Flags that switch `directive init` into headless manifest-emit mode (#2268):
20
+ * serialise the merged keystone `plan()` into a `{ version, files }` manifest
21
+ * with ALL execution side effects suppressed. Recognised in the USER argv; NOT
22
+ * part of the canonical always-applied argv above (which must stay a real,
23
+ * executing adoption dispatch — a headless flag there would silently suppress
24
+ * every install's side effects).
25
+ */
26
+ export const INIT_HEADLESS_FLAGS = ["--headless", "/headless"];
27
+ /**
28
+ * Flag naming the manifest output target for `--headless` (#2268). Accepts the
29
+ * `--output=<path>` and `--output <path>` forms; when absent the manifest is
30
+ * written to stdout. Writing this file is the ONLY filesystem side effect the
31
+ * headless path is permitted to perform.
32
+ */
33
+ export const INIT_OUTPUT_FLAGS = ["--output", "/output"];
5
34
  /** Canonical migrate argv: defaults to cwd, human-readable unless --json (#1941). */
6
35
  export const CANONICAL_MIGRATE_ARGV = ["--repo-root", "."];
36
+ /**
37
+ * Subcommand flag selecting the vendored→hybrid `.deft/core` un-commit (#2269).
38
+ * `migrate --untrack-core` dispatches to the destructive un-track path; bare
39
+ * `migrate` keeps its non-destructive provenance-stamp behavior.
40
+ */
41
+ export const MIGRATE_UNTRACK_CORE_FLAG = "--untrack-core";
7
42
  //# sourceMappingURL=constants.js.map
@@ -1,3 +1,26 @@
1
+ import { type HeadlessManifestSeams, type InitDispatchSeams } from "@deftai/directive-core/init-deposit";
1
2
  import type { DispatchIo } from "../dispatch.js";
2
- export declare function runInit(argv: readonly string[], io: DispatchIo): Promise<number>;
3
+ /** True when the user argv asked for a classify-only dispatch plan (`--dry-run`/`--plan`). */
4
+ export declare function isInitDryRun(argv: readonly string[]): boolean;
5
+ /** True when the user argv asked for headless manifest-emit mode (`--headless`). */
6
+ export declare function isInitHeadless(argv: readonly string[]): boolean;
7
+ /**
8
+ * Resolve the `--output=<path>` / `--output <path>` target for headless mode.
9
+ * Returns null when no output flag is present (manifest goes to stdout).
10
+ */
11
+ export declare function parseInitOutputPath(argv: readonly string[]): string | null;
12
+ /**
13
+ * `directive init` — the universal adoption dispatcher (#2265). Classifies the
14
+ * directory via the shared keystone plan() fact-set and dispatches to
15
+ * scaffold / brownfield-install / delegate-to-update / route-to-migrate.
16
+ *
17
+ * When `--headless` is present (#2268), init short-circuits the executing
18
+ * dispatch and instead serialises the merged `plan()` schema into a
19
+ * `{ version, files }` manifest with ALL execution side effects suppressed
20
+ * (no prompts, no git, no hook install, no writes outside `--output`).
21
+ *
22
+ * The optional `seams` argument is test-only injection for the dispatch path;
23
+ * `headlessSeams` injects the content-resolution seam for the headless path.
24
+ */
25
+ export declare function runInit(argv: readonly string[], io: DispatchIo, seams?: InitDispatchSeams, headlessSeams?: HeadlessManifestSeams): Promise<number>;
3
26
  //# sourceMappingURL=init.d.ts.map
@@ -1,11 +1,75 @@
1
- import { parseInitArgv, runInitDepositCli } from "@deftai/directive-core/init-deposit";
2
- import { CANONICAL_INIT_ARGV } from "./constants.js";
3
- export function runInit(argv, io) {
1
+ import { parseInitArgv, runInitDispatchCli, runInitHeadlessCli, } from "@deftai/directive-core/init-deposit";
2
+ import { CANONICAL_INIT_ARGV, INIT_DRY_RUN_FLAGS, INIT_HEADLESS_FLAGS, INIT_OUTPUT_FLAGS, } from "./constants.js";
3
+ /** True when the user argv asked for a classify-only dispatch plan (`--dry-run`/`--plan`). */
4
+ export function isInitDryRun(argv) {
5
+ const flags = INIT_DRY_RUN_FLAGS;
6
+ return argv.some((arg) => flags.includes(arg));
7
+ }
8
+ /** True when the user argv asked for headless manifest-emit mode (`--headless`). */
9
+ export function isInitHeadless(argv) {
10
+ const flags = INIT_HEADLESS_FLAGS;
11
+ return argv.some((arg) => flags.includes(arg));
12
+ }
13
+ /**
14
+ * Resolve the `--output=<path>` / `--output <path>` target for headless mode.
15
+ * Returns null when no output flag is present (manifest goes to stdout).
16
+ */
17
+ export function parseInitOutputPath(argv) {
18
+ const flags = INIT_OUTPUT_FLAGS;
19
+ for (let i = 0; i < argv.length; i += 1) {
20
+ const arg = argv[i];
21
+ if (arg === undefined)
22
+ continue;
23
+ for (const flag of flags) {
24
+ if (arg === flag) {
25
+ const next = argv[i + 1];
26
+ // A next token that is itself a flag (leading `-`) is NOT a path — treat
27
+ // `--output --headless` as a missing value rather than writing a file
28
+ // literally named `--headless`. Absolute POSIX paths (`/foo`) are kept.
29
+ if (next === undefined || next.length === 0 || next.startsWith("-"))
30
+ return null;
31
+ return next;
32
+ }
33
+ const prefix = `${flag}=`;
34
+ if (arg.startsWith(prefix)) {
35
+ const value = arg.slice(prefix.length);
36
+ return value.length > 0 ? value : null;
37
+ }
38
+ }
39
+ }
40
+ return null;
41
+ }
42
+ /**
43
+ * `directive init` — the universal adoption dispatcher (#2265). Classifies the
44
+ * directory via the shared keystone plan() fact-set and dispatches to
45
+ * scaffold / brownfield-install / delegate-to-update / route-to-migrate.
46
+ *
47
+ * When `--headless` is present (#2268), init short-circuits the executing
48
+ * dispatch and instead serialises the merged `plan()` schema into a
49
+ * `{ version, files }` manifest with ALL execution side effects suppressed
50
+ * (no prompts, no git, no hook install, no writes outside `--output`).
51
+ *
52
+ * The optional `seams` argument is test-only injection for the dispatch path;
53
+ * `headlessSeams` injects the content-resolution seam for the headless path.
54
+ */
55
+ export function runInit(argv, io, seams, headlessSeams) {
56
+ if (isInitHeadless(argv)) {
57
+ return runInitHeadlessCli({
58
+ outputPath: parseInitOutputPath(argv),
59
+ writeOut: io.writeOut,
60
+ writeErr: io.writeErr,
61
+ seams: headlessSeams,
62
+ });
63
+ }
4
64
  const args = parseInitArgv(CANONICAL_INIT_ARGV, argv);
5
- return runInitDepositCli({
6
- ...args,
65
+ return runInitDispatchCli({
66
+ projectDir: args.projectDir,
67
+ jsonOut: args.jsonOut,
68
+ nonInteractive: args.nonInteractive,
69
+ dryRun: isInitDryRun(argv),
7
70
  writeOut: io.writeOut,
8
71
  writeErr: io.writeErr,
72
+ seams,
9
73
  });
10
74
  }
11
75
  //# sourceMappingURL=init.js.map
@@ -1,9 +1,13 @@
1
- import { parseInitArgv, runMigrateCli } from "@deftai/directive-core/init-deposit";
2
- import { CANONICAL_MIGRATE_ARGV } from "./constants.js";
1
+ import { parseInitArgv, runMigrateCli, runUntrackCoreCli, } from "@deftai/directive-core/init-deposit";
2
+ import { CANONICAL_MIGRATE_ARGV, MIGRATE_UNTRACK_CORE_FLAG } from "./constants.js";
3
3
  /**
4
4
  * `directive migrate` (alias `deft migrate`) -- stage-2 provenance verb (#1941):
5
5
  * stamp a canonical-vendored `.deft/core` deposit as npm-managed. Thin wrapper
6
6
  * over the core orchestrator; maps the three-state result to a 0/1/2 exit code.
7
+ *
8
+ * `directive migrate --untrack-core` (#2269) branches to the vendored→hybrid
9
+ * un-commit path: `git rm --cached -r .deft/core` gated on a committed pin, plus
10
+ * `.gitignore` reconciliation. The destructive index mutation lives ONLY there.
7
11
  */
8
12
  const NO_EFFECT_CONFIRMATION_FLAGS = new Set([
9
13
  "--yes",
@@ -11,8 +15,21 @@ const NO_EFFECT_CONFIRMATION_FLAGS = new Set([
11
15
  "/yes",
12
16
  "/non-interactive",
13
17
  ]);
18
+ function hasUntrackCoreFlag(argv) {
19
+ return argv.some((arg) => arg === MIGRATE_UNTRACK_CORE_FLAG || arg === `/${MIGRATE_UNTRACK_CORE_FLAG.slice(2)}`);
20
+ }
14
21
  export function runMigrate(argv, io) {
15
22
  const args = parseInitArgv(CANONICAL_MIGRATE_ARGV, argv);
23
+ // `migrate --untrack-core` selects the destructive un-track subcommand;
24
+ // bare `migrate` keeps the provenance-stamp behavior below.
25
+ if (hasUntrackCoreFlag(argv)) {
26
+ return runUntrackCoreCli({
27
+ projectDir: args.projectDir,
28
+ jsonOut: args.jsonOut,
29
+ writeOut: io.writeOut,
30
+ writeErr: io.writeErr,
31
+ });
32
+ }
16
33
  // `migrate` reuses `parseInitArgv`, which understands the init/update headless
17
34
  // confirmation flags. migrate has no interactive prompts, so acknowledge the
18
35
  // flag rather than silently swallowing it (a natural reflex from init/update).
@@ -1,3 +1,5 @@
1
1
  import type { DispatchIo } from "../dispatch.js";
2
+ /** True when the user argv asked for a classify-only dry-run (`--dry-run`/`--plan`). */
3
+ export declare function isUpdateDryRun(argv: readonly string[]): boolean;
2
4
  export declare function runUpdate(argv: readonly string[], io: DispatchIo): Promise<number>;
3
5
  //# sourceMappingURL=update.d.ts.map
@@ -1,9 +1,15 @@
1
1
  import { parseUpdateArgv, runRefreshDepositCli } from "@deftai/directive-core/init-deposit";
2
- import { CANONICAL_UPDATE_ARGV } from "./constants.js";
2
+ import { CANONICAL_UPDATE_ARGV, UPDATE_DRY_RUN_FLAGS } from "./constants.js";
3
+ /** True when the user argv asked for a classify-only dry-run (`--dry-run`/`--plan`). */
4
+ export function isUpdateDryRun(argv) {
5
+ const flags = UPDATE_DRY_RUN_FLAGS;
6
+ return argv.some((arg) => flags.includes(arg));
7
+ }
3
8
  export function runUpdate(argv, io) {
4
9
  const args = parseUpdateArgv(CANONICAL_UPDATE_ARGV, argv);
5
10
  return runRefreshDepositCli({
6
11
  ...args,
12
+ dryRun: isUpdateDryRun(argv),
7
13
  writeOut: io.writeOut,
8
14
  writeErr: io.writeErr,
9
15
  });
@@ -3,6 +3,7 @@ export interface ParsedMigrateXbriefArgs {
3
3
  projectRoot: string;
4
4
  frameworkRoot: string;
5
5
  force: boolean;
6
+ keepLegacy: boolean;
6
7
  error?: string;
7
8
  }
8
9
  export declare function parseArgs(argv: readonly string[]): ParsedMigrateXbriefArgs;
@@ -6,11 +6,15 @@ export function parseArgs(argv) {
6
6
  let projectRoot = ".";
7
7
  let explicitFrameworkRoot;
8
8
  let force = false;
9
+ let keepLegacy = false;
9
10
  for (let i = 0; i < argv.length; i += 1) {
10
11
  const arg = argv[i] ?? "";
11
12
  if (arg === "--force") {
12
13
  force = true;
13
14
  }
15
+ else if (arg === "--keep-legacy") {
16
+ keepLegacy = true;
17
+ }
14
18
  else if (arg === "--project-root") {
15
19
  const value = argv[i + 1];
16
20
  if (value === undefined) {
@@ -18,6 +22,7 @@ export function parseArgs(argv) {
18
22
  projectRoot,
19
23
  frameworkRoot: resolveFrameworkRootForProject(projectRoot, explicitFrameworkRoot),
20
24
  force,
25
+ keepLegacy,
21
26
  error: "argument --project-root: expected one argument",
22
27
  };
23
28
  }
@@ -34,6 +39,7 @@ export function parseArgs(argv) {
34
39
  projectRoot,
35
40
  frameworkRoot: resolveFrameworkRootForProject(projectRoot, explicitFrameworkRoot),
36
41
  force,
42
+ keepLegacy,
37
43
  error: "argument --framework-root: expected one argument",
38
44
  };
39
45
  }
@@ -48,12 +54,13 @@ export function parseArgs(argv) {
48
54
  projectRoot,
49
55
  frameworkRoot: resolveFrameworkRootForProject(projectRoot, explicitFrameworkRoot),
50
56
  force,
57
+ keepLegacy,
51
58
  error: `unrecognized argument: ${arg}`,
52
59
  };
53
60
  }
54
61
  }
55
62
  const frameworkRoot = resolveFrameworkRootForProject(projectRoot, explicitFrameworkRoot);
56
- return { projectRoot, frameworkRoot, force };
63
+ return { projectRoot, frameworkRoot, force, keepLegacy };
57
64
  }
58
65
  export function run(argv) {
59
66
  const args = parseArgs(argv);
@@ -65,6 +72,7 @@ export function run(argv) {
65
72
  projectRoot: args.projectRoot,
66
73
  frameworkRoot: args.frameworkRoot,
67
74
  force: args.force,
75
+ keepLegacy: args.keepLegacy,
68
76
  }, {
69
77
  writeOut: (text) => {
70
78
  process.stdout.write(text);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive",
3
- "version": "0.68.1",
3
+ "version": "0.69.0",
4
4
  "description": "Directive CLI — npm install path for the Deft Directive framework.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -31,8 +31,8 @@
31
31
  "provenance": true
32
32
  },
33
33
  "dependencies": {
34
- "@deftai/directive-core": "^0.68.1",
35
- "@deftai/directive-content": "^0.68.1"
34
+ "@deftai/directive-core": "^0.69.0",
35
+ "@deftai/directive-content": "^0.69.0"
36
36
  },
37
37
  "scripts": {
38
38
  "build": "tsc -b"