@deftai/directive-core 0.59.0 → 0.60.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.
@@ -0,0 +1,126 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ const UNRELEASED_RE = /## \[Unreleased\][ \t]*\n([\s\S]*?)(?=\n## \[|$)/;
5
+ const CHANGE_NAME_RE = /^[\w][\w-]*$/;
6
+ const COMMIT_TYPES = "feat|fix|docs|chore|refactor|test|style|perf|ci|build|revert";
7
+ const COMMIT_SUBJECT_RE = new RegExp(`^(${COMMIT_TYPES})(\\(.+\\))?!?: .+`);
8
+ function proposalTemplate(name) {
9
+ return {
10
+ vBRIEFInfo: { version: "0.5" },
11
+ plan: {
12
+ title: name,
13
+ status: "draft",
14
+ narratives: {
15
+ Problem: "What is wrong or missing.",
16
+ Change: "What this proposal does about it.",
17
+ Scope: "In scope: ... Out of scope: ...",
18
+ Impact: "What existing code/specs are affected.",
19
+ Risks: "What could go wrong.",
20
+ Approach: "How to implement the change.",
21
+ Alternatives: "What else was considered and why not.",
22
+ Dependencies: "What must exist before this works.",
23
+ },
24
+ },
25
+ };
26
+ }
27
+ function tasksTemplate(name) {
28
+ return {
29
+ vBRIEFInfo: { version: "0.5" },
30
+ plan: { title: name, status: "draft", items: [], edges: [] },
31
+ };
32
+ }
33
+ /** Port of ``task change:changelog:check`` inline Python (#2022 Phase 2). */
34
+ export function runChangelogCheck(projectRoot, io) {
35
+ const path = join(resolve(projectRoot), "CHANGELOG.md");
36
+ if (!existsSync(path) || !statSync(path).isFile()) {
37
+ io.writeOut("FAIL: CHANGELOG.md not found\n");
38
+ return 1;
39
+ }
40
+ const text = readFileSync(path, "utf8");
41
+ const match = UNRELEASED_RE.exec(text);
42
+ if (match === null) {
43
+ io.writeOut("FAIL: No [Unreleased] section found in CHANGELOG.md\n");
44
+ return 1;
45
+ }
46
+ const body = match[1] ?? "";
47
+ const entries = body.split("\n").filter((line) => line.trimStart().startsWith("- "));
48
+ if (entries.length === 0) {
49
+ io.writeOut('FAIL: [Unreleased] section has no entries (no lines starting with "- ")\n');
50
+ return 1;
51
+ }
52
+ io.writeOut(`OK: CHANGELOG.md [Unreleased] section has ${entries.length} entries\n`);
53
+ return 0;
54
+ }
55
+ /** Port of ``task change:init`` inline Python (#2022 Phase 2). */
56
+ export function runChangeInit(projectRoot, name, io) {
57
+ const trimmed = name.trim();
58
+ if (trimmed.length === 0) {
59
+ io.writeOut("FAIL: Usage: task change:init -- <name>\n");
60
+ return 1;
61
+ }
62
+ if (!CHANGE_NAME_RE.test(trimmed)) {
63
+ io.writeOut("FAIL: Name must contain only alphanumeric characters, underscores, and hyphens\n");
64
+ return 1;
65
+ }
66
+ const base = join(resolve(projectRoot), "history", "changes", trimmed);
67
+ if (existsSync(base)) {
68
+ io.writeOut(`FAIL: ${base} already exists\n`);
69
+ return 1;
70
+ }
71
+ mkdirSync(join(base, "specs"), { recursive: true });
72
+ writeFileSync(join(base, "proposal.vbrief.json"), `${JSON.stringify(proposalTemplate(trimmed), null, 2)}\n`, "utf8");
73
+ writeFileSync(join(base, "tasks.vbrief.json"), `${JSON.stringify(tasksTemplate(trimmed), null, 2)}\n`, "utf8");
74
+ io.writeOut(`OK: Created change proposal at ${base}/\n`);
75
+ for (const file of ["proposal.vbrief.json", "tasks.vbrief.json", "specs/"]) {
76
+ io.writeOut(` - ${file}\n`);
77
+ }
78
+ return 0;
79
+ }
80
+ /** Port of ``task commit:lint`` inline Python (#2022 Phase 2). */
81
+ export function runCommitLint(projectRoot, io) {
82
+ let stdout;
83
+ try {
84
+ stdout = execFileSync("git", ["log", "--format=%B", "-1"], {
85
+ cwd: resolve(projectRoot),
86
+ encoding: "utf8",
87
+ });
88
+ }
89
+ catch {
90
+ io.writeOut("FAIL: Could not read HEAD commit message\n");
91
+ return 1;
92
+ }
93
+ const msg = stdout.trim();
94
+ const subject = msg.split("\n")[0] ?? "";
95
+ if (!COMMIT_SUBJECT_RE.test(subject)) {
96
+ io.writeOut("FAIL: Commit message does not match conventional commit format\n");
97
+ io.writeOut(` Got: ${subject}\n`);
98
+ io.writeOut(" Expected: type(scope): description\n");
99
+ io.writeOut(" Types: feat, fix, docs, chore, refactor, test, style, perf, ci, build, revert\n");
100
+ return 1;
101
+ }
102
+ io.writeOut("OK: Commit message is valid conventional commit\n");
103
+ io.writeOut(` Subject: ${subject}\n`);
104
+ return 0;
105
+ }
106
+ /** Port of ``task install:uninstall`` inline Python (#2022 Phase 2). */
107
+ export function runInstallUninstall(projectRoot, io) {
108
+ const path = join(resolve(projectRoot), "AGENTS.md");
109
+ if (!existsSync(path) || !statSync(path).isFile()) {
110
+ io.writeOut("No deft entry found in AGENTS.md\n");
111
+ return 0;
112
+ }
113
+ const original = readFileSync(path, "utf8");
114
+ const lines = original.split(/(?<=\n)/);
115
+ const filtered = lines.filter((line) => !line.startsWith("See deft/main.md") && !line.startsWith("Skills: deft/skills/"));
116
+ const next = filtered.join("");
117
+ if (next !== original) {
118
+ writeFileSync(path, next, "utf8");
119
+ io.writeOut("Removed deft entry from AGENTS.md\n");
120
+ }
121
+ else {
122
+ io.writeOut("No deft entry found in AGENTS.md\n");
123
+ }
124
+ return 0;
125
+ }
126
+ //# sourceMappingURL=index.js.map
@@ -1,9 +1,13 @@
1
1
  import { DEPRECATION_SENTINEL } from "../vbrief-build/constants.js";
2
2
  export { DEPRECATION_SENTINEL as DEPRECATED_REDIRECT_SENTINEL };
3
+ export declare function missingLifecycleFolders(projectRoot: string): string[];
3
4
  /** Return true when markdown content is a migration redirect stub. */
4
5
  export declare function isDeprecationRedirect(content: string): boolean;
6
+ export declare function isGeneratedSpecificationExport(projectRoot: string, content: string): boolean;
5
7
  /** Return true for a fully current ``task spec:render`` root export. */
6
8
  export declare function isCurrentGeneratedSpecification(projectRoot: string, content: string): boolean;
9
+ /** Return root artifact filenames that are legacy pre-v0.20 inputs (#793 / migrate preflight). */
10
+ export declare function detectPreCutoverLegacy(projectRoot: string): string[];
7
11
  /** Structured result of a pre-cutover (pre-v0.20 document model) probe. */
8
12
  export interface PrecutoverDetection {
9
13
  /** True when any legacy pre-v0.20 artifact still needs migration. */
@@ -7,7 +7,7 @@ const GENERATED_SPEC_PURPOSE = "<!-- Purpose: rendered specification -->";
7
7
  const GENERATED_SPEC_SOURCE = "<!-- Source of truth: vbrief/specification.vbrief.json -->";
8
8
  const SPEC_SOURCE_RELPATH = join("vbrief", "specification.vbrief.json");
9
9
  const LIFECYCLE_FOLDERS = ["proposed", "pending", "active", "completed", "cancelled"];
10
- function missingLifecycleFolders(projectRoot) {
10
+ export function missingLifecycleFolders(projectRoot) {
11
11
  const vbriefRoot = join(projectRoot, "vbrief");
12
12
  return LIFECYCLE_FOLDERS.filter((folder) => !existsSync(join(vbriefRoot, folder)));
13
13
  }
@@ -18,7 +18,7 @@ function hasCompleteLifecycle(projectRoot) {
18
18
  export function isDeprecationRedirect(content) {
19
19
  return content.includes(DEPRECATION_SENTINEL) || content.includes(DEPRECATION_REDIRECT_PURPOSE);
20
20
  }
21
- function isGeneratedSpecificationExport(projectRoot, content) {
21
+ export function isGeneratedSpecificationExport(projectRoot, content) {
22
22
  return (content.includes(GENERATED_SPEC_PURPOSE) &&
23
23
  content.includes(GENERATED_SPEC_SOURCE) &&
24
24
  existsSync(join(projectRoot, SPEC_SOURCE_RELPATH)));
@@ -43,6 +43,28 @@ function safeReadText(path) {
43
43
  return "";
44
44
  }
45
45
  }
46
+ function rootMarkdownIsLegacy(projectRoot, filename, content) {
47
+ if (isDeprecationRedirect(content))
48
+ return false;
49
+ if (filename === "SPECIFICATION.md" && isGeneratedSpecificationExport(projectRoot, content)) {
50
+ return false;
51
+ }
52
+ return filename === "SPECIFICATION.md" || filename === "PROJECT.md";
53
+ }
54
+ /** Return root artifact filenames that are legacy pre-v0.20 inputs (#793 / migrate preflight). */
55
+ export function detectPreCutoverLegacy(projectRoot) {
56
+ const legacy = [];
57
+ for (const filename of ["SPECIFICATION.md", "PROJECT.md"]) {
58
+ const candidate = join(projectRoot, filename);
59
+ if (!isFile(candidate))
60
+ continue;
61
+ const content = safeReadText(candidate);
62
+ if (rootMarkdownIsLegacy(projectRoot, filename, content)) {
63
+ legacy.push(filename);
64
+ }
65
+ }
66
+ return legacy;
67
+ }
46
68
  /**
47
69
  * Detect whether ``projectRoot`` still carries pre-v0.20 (pre-cutover) document-model
48
70
  * artifacts that need migration. Mirrors the AGENTS.md "Pre-Cutover Check" rule and the
@@ -2,6 +2,10 @@ export interface ToolCheck {
2
2
  readonly name: string;
3
3
  readonly command: readonly string[];
4
4
  }
5
+ export declare const MAINTAINER_TOOLS: readonly ToolCheck[];
6
+ /** Consumer npm-deposit toolchain (#2022 Phase 3) -- no Python/go/uv maintainer tools. */
7
+ export declare const CONSUMER_TOOLS: readonly ToolCheck[];
8
+ /** @deprecated use MAINTAINER_TOOLS */
5
9
  export declare const TOOLS: readonly ToolCheck[];
6
10
  export type CommandRunner = (command: readonly string[], timeoutMs: number) => {
7
11
  returncode: number;
@@ -23,6 +27,9 @@ export declare function defaultCommandRunner(command: readonly string[], timeout
23
27
  error: "not-found" | "exception";
24
28
  message: string;
25
29
  };
26
- /** Run maintainer toolchain probe (mirrors scripts/toolchain-check.py). */
27
- export declare function runToolchainCheck(runner?: CommandRunner, tools?: readonly ToolCheck[]): ToolchainCheckResult;
30
+ export interface ToolchainCheckOptions {
31
+ readonly consumer?: boolean;
32
+ }
33
+ /** Run maintainer or consumer toolchain probe (mirrors scripts/toolchain-check.py). */
34
+ export declare function runToolchainCheck(runner?: CommandRunner, options?: ToolchainCheckOptions, tools?: readonly ToolCheck[]): ToolchainCheckResult;
28
35
  //# sourceMappingURL=toolchain-check.d.ts.map
@@ -1,6 +1,6 @@
1
1
  import * as childProcess from "node:child_process";
2
2
  import { nodeRuntimeRemediationLines } from "./node-runtime.js";
3
- export const TOOLS = [
3
+ export const MAINTAINER_TOOLS = [
4
4
  { name: "go", command: ["go", "version"] },
5
5
  { name: "uv", command: ["uv", "--version"] },
6
6
  { name: "git", command: ["git", "--version"] },
@@ -8,6 +8,16 @@ export const TOOLS = [
8
8
  { name: "node", command: ["node", "--version"] },
9
9
  { name: "pnpm", command: ["pnpm", "--version"] },
10
10
  ];
11
+ /** Consumer npm-deposit toolchain (#2022 Phase 3) -- no Python/go/uv maintainer tools. */
12
+ export const CONSUMER_TOOLS = [
13
+ { name: "git", command: ["git", "--version"] },
14
+ { name: "gh", command: ["gh", "--version"] },
15
+ { name: "node", command: ["node", "--version"] },
16
+ { name: "pnpm", command: ["pnpm", "--version"] },
17
+ { name: "task", command: ["task", "--version"] },
18
+ ];
19
+ /** @deprecated use MAINTAINER_TOOLS */
20
+ export const TOOLS = MAINTAINER_TOOLS;
11
21
  const DEFAULT_TIMEOUT_MS = 10_000;
12
22
  export function defaultCommandRunner(command, timeoutMs) {
13
23
  try {
@@ -30,11 +40,12 @@ export function defaultCommandRunner(command, timeoutMs) {
30
40
  };
31
41
  }
32
42
  }
33
- /** Run maintainer toolchain probe (mirrors scripts/toolchain-check.py). */
34
- export function runToolchainCheck(runner = defaultCommandRunner, tools = TOOLS) {
43
+ /** Run maintainer or consumer toolchain probe (mirrors scripts/toolchain-check.py). */
44
+ export function runToolchainCheck(runner = defaultCommandRunner, options = {}, tools) {
45
+ const selectedTools = tools ?? (options.consumer ? CONSUMER_TOOLS : MAINTAINER_TOOLS);
35
46
  const lines = [];
36
47
  const failed = [];
37
- for (const tool of tools) {
48
+ for (const tool of selectedTools) {
38
49
  const result = runner(tool.command, DEFAULT_TIMEOUT_MS);
39
50
  if ("error" in result) {
40
51
  if (result.error === "not-found") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive-core",
3
- "version": "0.59.0",
3
+ "version": "0.60.0",
4
4
  "description": "TypeScript engine core for the Directive framework.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -190,6 +190,18 @@
190
190
  "types": "./dist/init-deposit/index.d.ts",
191
191
  "default": "./dist/init-deposit/index.js"
192
192
  },
193
+ "./migrate-preflight": {
194
+ "types": "./dist/migrate-preflight/index.d.ts",
195
+ "default": "./dist/migrate-preflight/index.js"
196
+ },
197
+ "./install-upgrade": {
198
+ "types": "./dist/install-upgrade/index.d.ts",
199
+ "default": "./dist/install-upgrade/index.js"
200
+ },
201
+ "./task-surface": {
202
+ "types": "./dist/task-surface/index.d.ts",
203
+ "default": "./dist/task-surface/index.js"
204
+ },
193
205
  "./dist/*.js": {
194
206
  "types": "./dist/*.d.ts",
195
207
  "default": "./dist/*.js"
@@ -209,8 +221,8 @@
209
221
  "provenance": true
210
222
  },
211
223
  "dependencies": {
212
- "@deftai/directive-content": "^0.59.0",
213
- "@deftai/directive-types": "^0.59.0"
224
+ "@deftai/directive-content": "^0.60.0",
225
+ "@deftai/directive-types": "^0.60.0"
214
226
  },
215
227
  "scripts": {
216
228
  "build": "tsc -b",