@deftai/directive-core 0.91.0 → 0.93.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.
Files changed (92) hide show
  1. package/dist/cache/fetch.js +3 -2
  2. package/dist/cache/io.d.ts +13 -2
  3. package/dist/cache/io.js +36 -7
  4. package/dist/cache/operations.js +5 -3
  5. package/dist/doctor/index.d.ts +1 -0
  6. package/dist/doctor/index.js +1 -0
  7. package/dist/doctor/main.js +49 -0
  8. package/dist/doctor/openclaw-l2-adapter.d.ts +26 -0
  9. package/dist/doctor/openclaw-l2-adapter.js +199 -0
  10. package/dist/hooks/dispatcher.d.ts +6 -1
  11. package/dist/hooks/dispatcher.js +30 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.js +2 -0
  14. package/dist/init-deposit/gitignore.js +3 -0
  15. package/dist/init-deposit/hygiene.d.ts +43 -1
  16. package/dist/init-deposit/hygiene.js +134 -10
  17. package/dist/init-deposit/index.d.ts +3 -0
  18. package/dist/init-deposit/index.js +3 -0
  19. package/dist/init-deposit/init-deposit.js +10 -0
  20. package/dist/init-deposit/refresh.js +11 -0
  21. package/dist/init-deposit/scaffold.js +6 -134
  22. package/dist/init-deposit/skill-discovery-deposit.d.ts +65 -0
  23. package/dist/init-deposit/skill-discovery-deposit.js +287 -0
  24. package/dist/init-deposit/skill-discovery-hosts.d.ts +94 -0
  25. package/dist/init-deposit/skill-discovery-hosts.js +217 -0
  26. package/dist/init-deposit/slash-deposit.d.ts +46 -0
  27. package/dist/init-deposit/slash-deposit.js +174 -0
  28. package/dist/orchestration/probe-session.d.ts +5 -1
  29. package/dist/orchestration/probe-session.js +24 -13
  30. package/dist/platform/agents-md.js +1 -1
  31. package/dist/policy/deft-directive-disable.d.ts +86 -0
  32. package/dist/policy/deft-directive-disable.js +167 -0
  33. package/dist/policy/delivery-branch.d.ts +33 -0
  34. package/dist/policy/delivery-branch.js +124 -0
  35. package/dist/policy/host-slash-commands.d.ts +28 -0
  36. package/dist/policy/host-slash-commands.js +103 -0
  37. package/dist/policy/index.d.ts +3 -0
  38. package/dist/policy/index.js +61 -5
  39. package/dist/scope/brief-io.d.ts +3 -1
  40. package/dist/scope/brief-io.js +5 -3
  41. package/dist/scope/delivery-evidence.d.ts +112 -0
  42. package/dist/scope/delivery-evidence.js +419 -0
  43. package/dist/scope/index.d.ts +1 -0
  44. package/dist/scope/index.js +1 -0
  45. package/dist/scope/main.d.ts +5 -0
  46. package/dist/scope/main.js +98 -3
  47. package/dist/scope/registry-artifact-sync.js +4 -1
  48. package/dist/scope/transition.d.ts +11 -1
  49. package/dist/scope/transition.js +30 -4
  50. package/dist/session/ritual-sentinel.js +21 -12
  51. package/dist/session/session-start-hook.d.ts +3 -0
  52. package/dist/session/session-start-hook.js +21 -0
  53. package/dist/session/session-start.js +31 -0
  54. package/dist/slash/emitters.d.ts +102 -0
  55. package/dist/slash/emitters.js +148 -0
  56. package/dist/slash/generator.d.ts +98 -0
  57. package/dist/slash/generator.js +145 -0
  58. package/dist/slash/index.d.ts +16 -0
  59. package/dist/slash/index.js +16 -0
  60. package/dist/slash/openclaw-adapter.d.ts +64 -0
  61. package/dist/slash/openclaw-adapter.js +198 -0
  62. package/dist/slash/openclaw-deposit.d.ts +73 -0
  63. package/dist/slash/openclaw-deposit.js +279 -0
  64. package/dist/slash/openclaw-slugs.d.ts +52 -0
  65. package/dist/slash/openclaw-slugs.js +126 -0
  66. package/dist/slash/product-set.d.ts +50 -0
  67. package/dist/slash/product-set.js +142 -0
  68. package/dist/swarm/complete-cohort.d.ts +20 -1
  69. package/dist/swarm/complete-cohort.js +56 -9
  70. package/dist/swarm/finalize-cohort-cli.js +9 -0
  71. package/dist/swarm/finalize-cohort.d.ts +8 -0
  72. package/dist/swarm/finalize-cohort.js +217 -21
  73. package/dist/user-config/experimental-rules.d.ts +43 -0
  74. package/dist/user-config/experimental-rules.js +162 -0
  75. package/dist/user-config/index.d.ts +1 -0
  76. package/dist/user-config/index.js +1 -0
  77. package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
  78. package/dist/vbrief-validate/plan-hooks.js +50 -0
  79. package/dist/verify-source/contained-writes.js +1 -1
  80. package/dist/xbrief/create.d.ts +36 -0
  81. package/dist/xbrief/create.js +285 -0
  82. package/dist/xbrief/index.d.ts +14 -0
  83. package/dist/xbrief/index.js +42 -0
  84. package/dist/xbrief/paths.d.ts +37 -0
  85. package/dist/xbrief/paths.js +123 -0
  86. package/dist/xbrief/styles.d.ts +36 -0
  87. package/dist/xbrief/styles.js +235 -0
  88. package/dist/xbrief/types.d.ts +50 -0
  89. package/dist/xbrief/types.js +17 -0
  90. package/dist/xbrief/verify.d.ts +30 -0
  91. package/dist/xbrief/verify.js +251 -0
  92. package/package.json +11 -3
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Safe USER.md Experimental Rules toggle (#46).
3
+ *
4
+ * Pure string helpers that add/remove only the three canonical meta-philosophy
5
+ * reference lines (SOUL / morals / code-field). Personal and Defaults section
6
+ * bodies are never rewritten — only the `## Experimental Rules` region changes.
7
+ *
8
+ * Detection matches lines under `## Experimental Rules` that contain the deposit
9
+ * path (`meta/SOUL.md`, …); enable writes the canonical setup-skill bullet.
10
+ * Custom bullets for other topics in the same section are preserved.
11
+ */
12
+ /** Canonical Experimental Rules entries (setup Phase 1 steps 5a–5c). */
13
+ export const EXPERIMENTAL_META_ENTRIES = [
14
+ {
15
+ id: "soul",
16
+ path: "meta/SOUL.md",
17
+ line: "- ! Use meta/SOUL.md for strategic context and purpose-driven guidance",
18
+ },
19
+ {
20
+ id: "morals",
21
+ path: "meta/morals.md",
22
+ line: "- ! Use meta/morals.md for ethical AI development principles",
23
+ },
24
+ {
25
+ id: "code-field",
26
+ path: "meta/code-field.md",
27
+ line: "- ~ Use meta/code-field.md for advanced architecture patterns",
28
+ },
29
+ ];
30
+ const SECTION_HEADING = "## Experimental Rules";
31
+ function detectNewline(text) {
32
+ return text.includes("\r\n") ? "\r\n" : "\n";
33
+ }
34
+ function lineMentionsPath(line, path) {
35
+ return line.includes(path);
36
+ }
37
+ function findExperimentalSection(text) {
38
+ const re = /^## Experimental Rules[ \t]*\r?\n?/m;
39
+ const match = re.exec(text);
40
+ if (!match || match.index === undefined) {
41
+ return null;
42
+ }
43
+ const start = match.index;
44
+ const afterHeading = start + match[0].length;
45
+ const rest = text.slice(afterHeading);
46
+ // Next H2 or a horizontal rule at line start ends the section.
47
+ const next = /^(## |---[ \t]*$)/m.exec(rest);
48
+ const end = next && next.index !== undefined ? afterHeading + next.index : text.length;
49
+ return {
50
+ start,
51
+ end,
52
+ full: text.slice(start, end),
53
+ body: text.slice(afterHeading, end),
54
+ };
55
+ }
56
+ /**
57
+ * Parse on/off state for the three experimental meta entries.
58
+ * A path is ON only when a line under `## Experimental Rules` mentions it —
59
+ * mentions in Personal/Defaults/prose do not count (writes are section-scoped).
60
+ */
61
+ export function parseExperimentalRulesState(userMdText) {
62
+ const state = {
63
+ soul: false,
64
+ morals: false,
65
+ "code-field": false,
66
+ };
67
+ const section = findExperimentalSection(userMdText);
68
+ if (!section) {
69
+ return state;
70
+ }
71
+ for (const line of section.body.split(/\r?\n/)) {
72
+ for (const entry of EXPERIMENTAL_META_ENTRIES) {
73
+ if (lineMentionsPath(line, entry.path)) {
74
+ state[entry.id] = true;
75
+ }
76
+ }
77
+ }
78
+ return state;
79
+ }
80
+ /**
81
+ * Build section body lines: preserve non-meta custom bullets; apply desired
82
+ * on/off for the three canonical paths; stable order soul → morals → code-field.
83
+ */
84
+ function buildSectionBody(existingBody, desired, nl) {
85
+ const rawLines = existingBody.split(/\r?\n/);
86
+ const custom = [];
87
+ for (const line of rawLines) {
88
+ if (line.trim() === "") {
89
+ continue;
90
+ }
91
+ const isMeta = EXPERIMENTAL_META_ENTRIES.some((e) => lineMentionsPath(line, e.path));
92
+ if (!isMeta) {
93
+ custom.push(line);
94
+ }
95
+ }
96
+ const metaLines = EXPERIMENTAL_META_ENTRIES.filter((e) => desired[e.id]).map((e) => e.line);
97
+ const bullets = [...metaLines, ...custom];
98
+ if (bullets.length === 0) {
99
+ return "";
100
+ }
101
+ // Heading + blank line + bullets + trailing blank line for clean separation.
102
+ return `${SECTION_HEADING}${nl}${nl}${bullets.join(nl)}${nl}`;
103
+ }
104
+ /**
105
+ * Insert a new Experimental Rules section before the trailing `---` / Note
106
+ * block when present; otherwise append after the last non-empty content.
107
+ */
108
+ function insertSection(text, section, nl) {
109
+ // Prefer before the trailing horizontal rule that precedes the USER.md Note
110
+ // block (allow blank lines between `---` and `**Note**`).
111
+ const noteRe = /\r?\n---[ \t]*\r?\n(?:[ \t]*\r?\n)*[ \t]*\*\*Note\*\*/;
112
+ const noteMatch = noteRe.exec(text);
113
+ if (noteMatch && noteMatch.index !== undefined) {
114
+ const before = text.slice(0, noteMatch.index).replace(/[ \t]+$/u, "");
115
+ const after = text.slice(noteMatch.index);
116
+ const sep = before.endsWith(nl) ? nl : `${nl}${nl}`;
117
+ return `${before}${sep}${section.replace(/\s+$/u, "")}${after}`;
118
+ }
119
+ const trimmed = text.replace(/\s+$/u, "");
120
+ return `${trimmed}${nl}${nl}${section.replace(/\s+$/u, "")}${nl}`;
121
+ }
122
+ /**
123
+ * Apply desired Experimental Rules on/off state.
124
+ *
125
+ * - Only mutates the `## Experimental Rules` region (or inserts/removes it).
126
+ * - Personal / Defaults content is left byte-identical outside that region.
127
+ * - Enabling uses the canonical setup-skill lines; disabling drops path matches.
128
+ * - Custom non-meta bullets under the section are preserved.
129
+ * - When all three are off and no custom bullets remain, the section is removed.
130
+ */
131
+ export function applyExperimentalRulesState(userMdText, desired) {
132
+ const nl = detectNewline(userMdText);
133
+ const sectionText = buildSectionBody(findExperimentalSection(userMdText)?.body ?? "", desired, nl);
134
+ const existing = findExperimentalSection(userMdText);
135
+ if (existing) {
136
+ if (!sectionText) {
137
+ // Remove section; tidy surrounding blank lines.
138
+ const before = userMdText.slice(0, existing.start).replace(/[ \t]+$/u, "");
139
+ let after = userMdText.slice(existing.end);
140
+ // Collapse to at most two newlines at the join.
141
+ const beforeCore = before.replace(/(\r?\n){2,}$/u, nl);
142
+ after = after.replace(/^(\r?\n)+/u, nl);
143
+ if (after.startsWith("---") || after.startsWith("## ")) {
144
+ return `${beforeCore.replace(/(\r?\n)+$/u, nl)}${nl}${after}`;
145
+ }
146
+ return `${beforeCore}${after}`;
147
+ }
148
+ return userMdText.slice(0, existing.start) + sectionText + userMdText.slice(existing.end);
149
+ }
150
+ if (!sectionText) {
151
+ return userMdText;
152
+ }
153
+ return insertSection(userMdText, sectionText, nl);
154
+ }
155
+ /**
156
+ * Toggle a single experimental meta entry; leave the other two unchanged.
157
+ */
158
+ export function setExperimentalRule(userMdText, id, enabled) {
159
+ const current = parseExperimentalRulesState(userMdText);
160
+ return applyExperimentalRulesState(userMdText, { ...current, [id]: enabled });
161
+ }
162
+ //# sourceMappingURL=experimental-rules.js.map
@@ -1,2 +1,3 @@
1
+ export * from "./experimental-rules.js";
1
2
  export * from "./resolve-user-md.js";
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1,3 @@
1
+ export * from "./experimental-rules.js";
1
2
  export * from "./resolve-user-md.js";
2
3
  //# sourceMappingURL=index.js.map
@@ -8,6 +8,10 @@ export declare function validateTriageRankingLabelsOnPlan(plan: unknown, filepat
8
8
  export declare function validateRuntimeAuthorityOnPlan(plan: unknown, filepath: string): string[];
9
9
  /** vbrief_validate hook: validate ``plan.policy.hostHooks`` (#2752). */
10
10
  export declare function validateHostHooksOnPlan(plan: unknown, filepath: string): string[];
11
+ /** vbrief_validate hook: validate ``plan.policy.hostSlashCommands`` (#3054). */
12
+ export declare function validateHostSlashCommandsOnPlan(plan: unknown, filepath: string): string[];
13
+ /** vbrief_validate hook: validate ``plan.policy.openClawProductCommands`` (#3064). */
14
+ export declare function validateOpenClawProductCommandsOnPlan(plan: unknown, filepath: string): string[];
11
15
  /** vbrief_validate hook: validate ``plan.policy.stalenessTickler`` (#2489). */
12
16
  export declare function validateStalenessTicklerOnPlan(plan: unknown, filepath: string): string[];
13
17
  /** Run all PROJECT-DEFINITION policy hooks (mirrors lazy-import block in Python). */
@@ -1,7 +1,9 @@
1
1
  import { validateHostHooks } from "../policy/host-hooks.js";
2
+ import { validateHostSlashCommands } from "../policy/host-slash-commands.js";
2
3
  import { readPlanPolicy } from "../policy/plan-extensions.js";
3
4
  import { validateRuntimeAuthority } from "../policy/runtime-authority.js";
4
5
  import { validateStalenessTickler } from "../policy/staleness-tickler.js";
6
+ import { validateOpenClawProductCommands } from "../slash/openclaw-deposit.js";
5
7
  import { validateTriageAutoClassifyOnPlan, validateTriageHoldMarkersOnPlan, } from "../triage/classify/index.js";
6
8
  import { validateRankingLabels } from "../triage/queue/ranking-labels.js";
7
9
  import { pyStrRepr, pythonTypeName } from "../triage/scope/python-repr.js";
@@ -140,6 +142,42 @@ export function validateHostHooksOnPlan(plan, filepath) {
140
142
  }
141
143
  return out;
142
144
  }
145
+ /** vbrief_validate hook: validate ``plan.policy.hostSlashCommands`` (#3054). */
146
+ export function validateHostSlashCommandsOnPlan(plan, filepath) {
147
+ if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
148
+ return [];
149
+ }
150
+ const policy = readPlanPolicy(plan);
151
+ if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
152
+ return [];
153
+ }
154
+ if (!("hostSlashCommands" in policy)) {
155
+ return [];
156
+ }
157
+ const out = [];
158
+ for (const err of validateHostSlashCommands(policy.hostSlashCommands)) {
159
+ out.push(`${filepath}: ${err} (#3054)`);
160
+ }
161
+ return out;
162
+ }
163
+ /** vbrief_validate hook: validate ``plan.policy.openClawProductCommands`` (#3064). */
164
+ export function validateOpenClawProductCommandsOnPlan(plan, filepath) {
165
+ if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
166
+ return [];
167
+ }
168
+ const policy = readPlanPolicy(plan);
169
+ if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
170
+ return [];
171
+ }
172
+ if (!("openClawProductCommands" in policy)) {
173
+ return [];
174
+ }
175
+ const out = [];
176
+ for (const err of validateOpenClawProductCommands(policy.openClawProductCommands)) {
177
+ out.push(`${filepath}: ${err} (#3064)`);
178
+ }
179
+ return out;
180
+ }
143
181
  /** vbrief_validate hook: validate ``plan.policy.stalenessTickler`` (#2489). */
144
182
  export function validateStalenessTicklerOnPlan(plan, filepath) {
145
183
  if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
@@ -216,6 +254,18 @@ export function runProjectDefinitionHooks(plan, filepath) {
216
254
  catch {
217
255
  /* hook must not break validation */
218
256
  }
257
+ try {
258
+ errors.push(...validateHostSlashCommandsOnPlan(plan, filepath));
259
+ }
260
+ catch {
261
+ /* hook must not break validation */
262
+ }
263
+ try {
264
+ errors.push(...validateOpenClawProductCommandsOnPlan(plan, filepath));
265
+ }
266
+ catch {
267
+ /* hook must not break validation */
268
+ }
219
269
  return errors;
220
270
  }
221
271
  //# sourceMappingURL=plan-hooks.js.map
@@ -59,7 +59,7 @@ export const CONTAINED_WRITES_ALLOWLIST = [
59
59
  "packages/core/src/intake/issue-ingest.ts",
60
60
  "packages/core/src/intake/reconcile-issues.ts",
61
61
  "packages/core/src/issue-sync/sync-from-xbrief.ts",
62
- "packages/core/src/orchestration/probe-session.ts",
62
+ // probe-session.ts removed from allowlist after #3042 contained writeSession.
63
63
  "packages/core/src/orchestration/verify-judgment-gates.ts",
64
64
  "packages/core/src/platform/changelog-cli.ts",
65
65
  "packages/core/src/release/gh.ts",
@@ -0,0 +1,36 @@
1
+ /**
2
+ * xbrief:create — write a dense xBRIEF artifact at --out (#3057).
3
+ *
4
+ * Required: --format (json|md|both), --out
5
+ * Optional: --style, --title, --id, --status, --description, --force, --project-root
6
+ *
7
+ * Does NOT move lifecycle folders. Not scope:promote / activate / complete.
8
+ */
9
+ import { type XbriefCliResult, type XbriefFormat, type XbriefStyle } from "./types.js";
10
+ export declare const CREATE_USAGE: string;
11
+ export interface CreateOptions {
12
+ format: XbriefFormat;
13
+ out: string;
14
+ style: XbriefStyle;
15
+ title?: string;
16
+ id?: string;
17
+ status?: string;
18
+ description?: string;
19
+ fromJson?: string;
20
+ force?: boolean;
21
+ projectRoot: string;
22
+ sizeCapBytes?: number;
23
+ cwd?: string;
24
+ home?: string;
25
+ env?: NodeJS.ProcessEnv;
26
+ now?: Date;
27
+ }
28
+ /** Parse create CLI argv into options (or error string). */
29
+ export declare function parseCreateArgv(argv: readonly string[]): CreateOptions | {
30
+ error: string;
31
+ };
32
+ /** Core create implementation (testable). */
33
+ export declare function createXbrief(options: CreateOptions): XbriefCliResult;
34
+ /** CLI entry for dispatch wrapper. */
35
+ export declare function runXbriefCreateCli(argv: string[]): XbriefCliResult;
36
+ //# sourceMappingURL=create.d.ts.map
@@ -0,0 +1,285 @@
1
+ /**
2
+ * xbrief:create — write a dense xBRIEF artifact at --out (#3057).
3
+ *
4
+ * Required: --format (json|md|both), --out
5
+ * Optional: --style, --title, --id, --status, --description, --force, --project-root
6
+ *
7
+ * Does NOT move lifecycle folders. Not scope:promote / activate / complete.
8
+ */
9
+ import { readFileSync } from "node:fs";
10
+ import { basename } from "node:path";
11
+ import { ContainedWriteError, ContainedWriteErrorCode, containedWrite, } from "../fs/contained-write.js";
12
+ import { validateVbriefSchema } from "../vbrief-validate/schema.js";
13
+ import { resolveXbriefOutPaths, XbriefPathError } from "./paths.js";
14
+ import { buildStyleDocument, renderMarkdown } from "./styles.js";
15
+ import { DEFAULT_XBRIEF_SIZE_CAP_BYTES, isXbriefFormat, isXbriefStyle, } from "./types.js";
16
+ export const CREATE_USAGE = "Usage: deft xbrief:create -- --format <json|md|both> --out <path> [--style <scope|playbook|mission|project>] [--title <t>] [--id <id>] [--status <s>] [--description <d>] [--from-json <path>] [--force] [--project-root <dir>]\n" +
17
+ " Write a dense xBRIEF artifact at --out. Required: --format and --out.\n" +
18
+ " create ≠ lifecycle: does not promote/activate/complete (use scope:* for lifecycle).\n";
19
+ function fail(stderr, exitCode = 2) {
20
+ return { exitCode, stdout: "", stderr };
21
+ }
22
+ function parseFlagValue(argv, i, name) {
23
+ const eq = argv[i]?.startsWith(`${name}=`) ? argv[i].slice(name.length + 1) : undefined;
24
+ if (eq !== undefined) {
25
+ if (eq.length === 0)
26
+ return { error: `argument ${name}: expected one argument\n` };
27
+ return { value: eq, next: i };
28
+ }
29
+ const next = argv[i + 1];
30
+ if (next === undefined || next.startsWith("-")) {
31
+ return { error: `argument ${name}: expected one argument\n` };
32
+ }
33
+ return { value: next, next: i + 1 };
34
+ }
35
+ /** Parse create CLI argv into options (or error string). */
36
+ export function parseCreateArgv(argv) {
37
+ let format;
38
+ let out;
39
+ let style = "scope";
40
+ let title;
41
+ let id;
42
+ let status;
43
+ let description;
44
+ let fromJson;
45
+ let force = false;
46
+ let projectRoot = process.cwd();
47
+ for (let i = 0; i < argv.length; i += 1) {
48
+ const arg = argv[i];
49
+ if (arg === undefined)
50
+ continue;
51
+ if (arg === "-h" || arg === "--help") {
52
+ return { error: CREATE_USAGE };
53
+ }
54
+ if (arg === "--force") {
55
+ force = true;
56
+ continue;
57
+ }
58
+ if (arg === "--format" || arg.startsWith("--format=")) {
59
+ const parsed = parseFlagValue(argv, i, "--format");
60
+ if ("error" in parsed)
61
+ return parsed;
62
+ format = parsed.value;
63
+ i = parsed.next;
64
+ continue;
65
+ }
66
+ if (arg === "--out" || arg.startsWith("--out=")) {
67
+ const parsed = parseFlagValue(argv, i, "--out");
68
+ if ("error" in parsed)
69
+ return parsed;
70
+ out = parsed.value;
71
+ i = parsed.next;
72
+ continue;
73
+ }
74
+ if (arg === "--style" || arg.startsWith("--style=")) {
75
+ const parsed = parseFlagValue(argv, i, "--style");
76
+ if ("error" in parsed)
77
+ return parsed;
78
+ style = parsed.value;
79
+ i = parsed.next;
80
+ continue;
81
+ }
82
+ if (arg === "--title" || arg.startsWith("--title=")) {
83
+ const parsed = parseFlagValue(argv, i, "--title");
84
+ if ("error" in parsed)
85
+ return parsed;
86
+ title = parsed.value;
87
+ i = parsed.next;
88
+ continue;
89
+ }
90
+ if (arg === "--id" || arg.startsWith("--id=")) {
91
+ const parsed = parseFlagValue(argv, i, "--id");
92
+ if ("error" in parsed)
93
+ return parsed;
94
+ id = parsed.value;
95
+ i = parsed.next;
96
+ continue;
97
+ }
98
+ if (arg === "--status" || arg.startsWith("--status=")) {
99
+ const parsed = parseFlagValue(argv, i, "--status");
100
+ if ("error" in parsed)
101
+ return parsed;
102
+ status = parsed.value;
103
+ i = parsed.next;
104
+ continue;
105
+ }
106
+ if (arg === "--description" || arg.startsWith("--description=")) {
107
+ const parsed = parseFlagValue(argv, i, "--description");
108
+ if ("error" in parsed)
109
+ return parsed;
110
+ description = parsed.value;
111
+ i = parsed.next;
112
+ continue;
113
+ }
114
+ if (arg === "--from-json" || arg.startsWith("--from-json=")) {
115
+ const parsed = parseFlagValue(argv, i, "--from-json");
116
+ if ("error" in parsed)
117
+ return parsed;
118
+ fromJson = parsed.value;
119
+ i = parsed.next;
120
+ continue;
121
+ }
122
+ if (arg === "--project-root" || arg.startsWith("--project-root=")) {
123
+ const parsed = parseFlagValue(argv, i, "--project-root");
124
+ if ("error" in parsed)
125
+ return parsed;
126
+ projectRoot = parsed.value;
127
+ i = parsed.next;
128
+ continue;
129
+ }
130
+ if (arg === "--")
131
+ continue;
132
+ return { error: `unrecognized argument: ${arg}\n${CREATE_USAGE}` };
133
+ }
134
+ if (format === undefined) {
135
+ return { error: `missing required --format (json|md|both)\n${CREATE_USAGE}` };
136
+ }
137
+ if (!isXbriefFormat(format)) {
138
+ return { error: `invalid --format ${format} (expected json|md|both)\n${CREATE_USAGE}` };
139
+ }
140
+ if (out === undefined || out.length === 0) {
141
+ return { error: `missing required --out\n${CREATE_USAGE}` };
142
+ }
143
+ if (!isXbriefStyle(style)) {
144
+ return {
145
+ error: `invalid --style ${style} (expected scope|playbook|mission|project)\n${CREATE_USAGE}`,
146
+ };
147
+ }
148
+ return {
149
+ format,
150
+ out,
151
+ style,
152
+ title,
153
+ id,
154
+ status,
155
+ description,
156
+ fromJson,
157
+ force,
158
+ projectRoot,
159
+ };
160
+ }
161
+ function loadFromJson(path) {
162
+ try {
163
+ const raw = readFileSync(path, "utf8");
164
+ const data = JSON.parse(raw);
165
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
166
+ return { ok: false, error: `--from-json must be a JSON object: ${path}\n` };
167
+ }
168
+ return { ok: true, doc: data };
169
+ }
170
+ catch (err) {
171
+ const msg = err instanceof Error ? err.message : String(err);
172
+ return { ok: false, error: `failed to read --from-json ${path}: ${msg}\n` };
173
+ }
174
+ }
175
+ function defaultTitle(style, outStem) {
176
+ const base = basename(outStem);
177
+ if (base.length > 0)
178
+ return base;
179
+ return `Untitled ${style}`;
180
+ }
181
+ /** Core create implementation (testable). */
182
+ export function createXbrief(options) {
183
+ let paths;
184
+ try {
185
+ paths = resolveXbriefOutPaths({
186
+ projectRoot: options.projectRoot,
187
+ out: options.out,
188
+ format: options.format,
189
+ cwd: options.cwd,
190
+ home: options.home,
191
+ env: options.env,
192
+ });
193
+ }
194
+ catch (err) {
195
+ if (err instanceof XbriefPathError) {
196
+ return fail(`${err.message}\n`, 1);
197
+ }
198
+ throw err;
199
+ }
200
+ let doc;
201
+ if (options.fromJson !== undefined) {
202
+ const loaded = loadFromJson(options.fromJson);
203
+ if (!loaded.ok)
204
+ return fail(loaded.error, 1);
205
+ doc = loaded.doc;
206
+ }
207
+ else {
208
+ const title = options.title ?? defaultTitle(options.style, paths.stemAbs);
209
+ doc = buildStyleDocument({
210
+ style: options.style,
211
+ title,
212
+ id: options.id,
213
+ status: options.status,
214
+ description: options.description,
215
+ now: options.now,
216
+ });
217
+ }
218
+ const schemaErrors = validateVbriefSchema(doc, paths.stemAbs);
219
+ if (schemaErrors.length > 0) {
220
+ return fail(`xbrief:create schema invalid:\n${schemaErrors.map((e) => ` - ${e}`).join("\n")}\n`, 1);
221
+ }
222
+ const sizeCap = options.sizeCapBytes ?? DEFAULT_XBRIEF_SIZE_CAP_BYTES;
223
+ const jsonText = `${JSON.stringify(doc, null, 2)}\n`;
224
+ const mdText = renderMarkdown(doc, options.style);
225
+ if (Buffer.byteLength(jsonText, "utf8") > sizeCap) {
226
+ return fail(`xbrief:create refused: json payload exceeds size cap (${sizeCap} bytes)\n`, 1);
227
+ }
228
+ if (Buffer.byteLength(mdText, "utf8") > sizeCap) {
229
+ return fail(`xbrief:create refused: md payload exceeds size cap (${sizeCap} bytes)\n`, 1);
230
+ }
231
+ const mode = options.force === true ? "replace" : "create";
232
+ const written = [];
233
+ try {
234
+ if (paths.jsonAbs !== null) {
235
+ containedWrite({
236
+ root: paths.projectRoot,
237
+ target: paths.jsonAbs,
238
+ data: jsonText,
239
+ mode,
240
+ });
241
+ written.push(paths.jsonAbs);
242
+ }
243
+ if (paths.mdAbs !== null) {
244
+ containedWrite({
245
+ root: paths.projectRoot,
246
+ target: paths.mdAbs,
247
+ data: mdText,
248
+ mode,
249
+ });
250
+ written.push(paths.mdAbs);
251
+ }
252
+ }
253
+ catch (err) {
254
+ if (err instanceof ContainedWriteError) {
255
+ if (err.code === ContainedWriteErrorCode.EXISTS) {
256
+ return fail(`xbrief:create refused: target exists (${err.target}); pass --force to replace\n`, 1);
257
+ }
258
+ if (err.code === ContainedWriteErrorCode.ESCAPE) {
259
+ return fail(`xbrief:create path refused: ${err.message}\n`, 1);
260
+ }
261
+ return fail(`xbrief:create write failed: ${err.message}\n`, 1);
262
+ }
263
+ throw err;
264
+ }
265
+ const lines = [
266
+ `OK xbrief:create format=${options.format} style=${options.style}`,
267
+ ...written.map((p) => ` wrote ${p}`),
268
+ " note: create is not a lifecycle move (scope:* handles promote/activate/complete)",
269
+ ];
270
+ return { exitCode: 0, stdout: `${lines.join("\n")}\n`, stderr: "" };
271
+ }
272
+ /** CLI entry for dispatch wrapper. */
273
+ export function runXbriefCreateCli(argv) {
274
+ const parsed = parseCreateArgv(argv);
275
+ if ("error" in parsed) {
276
+ const isHelp = parsed.error === CREATE_USAGE;
277
+ return {
278
+ exitCode: isHelp ? 0 : 2,
279
+ stdout: isHelp ? CREATE_USAGE : "",
280
+ stderr: isHelp ? "" : parsed.error,
281
+ };
282
+ }
283
+ return createXbrief(parsed);
284
+ }
285
+ //# sourceMappingURL=create.js.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * On-demand xBRIEF create/verify surface (#3057).
3
+ *
4
+ * create/verify write or check dense SoT artifacts at --out.
5
+ * Scope lifecycle remains scope:promote / activate / complete / etc.
6
+ */
7
+ export { CREATE_USAGE, type CreateOptions, createXbrief, parseCreateArgv, runXbriefCreateCli, } from "./create.js";
8
+ export { expandUserPath, resolveXbriefOutPaths, stripXbriefSuffix, XbriefPathError, } from "./paths.js";
9
+ export { type BuildDocumentInput, buildStyleDocument, MD_REQUIRED_SECTIONS, parseMarkdownMeta, renderMarkdown, } from "./styles.js";
10
+ export { DEFAULT_XBRIEF_SIZE_CAP_BYTES, isXbriefFormat, isXbriefStyle, XBRIEF_FORMATS, XBRIEF_STYLES, type XbriefCliResult, type XbriefDocument, type XbriefFormat, type XbriefPaths, type XbriefStyle, } from "./types.js";
11
+ export { parseVerifyArgv, runXbriefVerifyCli, VERIFY_USAGE, type VerifyOptions, verifyXbrief, } from "./verify.js";
12
+ /** Dispatch-compatible main for a combined xbrief-cli module (create|verify). */
13
+ export declare function main(argv?: string[]): number;
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,42 @@
1
+ /**
2
+ * On-demand xBRIEF create/verify surface (#3057).
3
+ *
4
+ * create/verify write or check dense SoT artifacts at --out.
5
+ * Scope lifecycle remains scope:promote / activate / complete / etc.
6
+ */
7
+ import { runXbriefCreateCli } from "./create.js";
8
+ import { runXbriefVerifyCli } from "./verify.js";
9
+ export { CREATE_USAGE, createXbrief, parseCreateArgv, runXbriefCreateCli, } from "./create.js";
10
+ export { expandUserPath, resolveXbriefOutPaths, stripXbriefSuffix, XbriefPathError, } from "./paths.js";
11
+ export { buildStyleDocument, MD_REQUIRED_SECTIONS, parseMarkdownMeta, renderMarkdown, } from "./styles.js";
12
+ export { DEFAULT_XBRIEF_SIZE_CAP_BYTES, isXbriefFormat, isXbriefStyle, XBRIEF_FORMATS, XBRIEF_STYLES, } from "./types.js";
13
+ export { parseVerifyArgv, runXbriefVerifyCli, VERIFY_USAGE, verifyXbrief, } from "./verify.js";
14
+ /** Dispatch-compatible main for a combined xbrief-cli module (create|verify). */
15
+ export function main(argv = process.argv.slice(2)) {
16
+ const [verb, ...rest] = argv;
17
+ if (verb === "create") {
18
+ const result = runXbriefCreateCli(rest);
19
+ if (result.stdout)
20
+ process.stdout.write(result.stdout);
21
+ if (result.stderr)
22
+ process.stderr.write(result.stderr);
23
+ return result.exitCode;
24
+ }
25
+ if (verb === "verify") {
26
+ const result = runXbriefVerifyCli(rest);
27
+ if (result.stdout)
28
+ process.stdout.write(result.stdout);
29
+ if (result.stderr)
30
+ process.stderr.write(result.stderr);
31
+ return result.exitCode;
32
+ }
33
+ if (verb === "-h" || verb === "--help" || verb === undefined) {
34
+ process.stdout.write("Usage: deft xbrief:create|xbrief:verify -- --format <json|md|both> --out <path> [options]\n" +
35
+ " create/verify dense xBRIEF artifacts (not scope lifecycle).\n" +
36
+ " See: deft xbrief:create --help | deft xbrief:verify --help\n");
37
+ return verb === undefined ? 2 : 0;
38
+ }
39
+ process.stderr.write(`unknown xbrief subcommand: ${verb}\n`);
40
+ return 2;
41
+ }
42
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Portable path expand + project-root containment for xbrief:create/verify (#3057).
3
+ *
4
+ * Uses real path APIs (node:path / node:os) — no slash-only logic.
5
+ * Default containment under project root fails closed on escape.
6
+ */
7
+ import type { XbriefFormat, XbriefPaths } from "./types.js";
8
+ export declare class XbriefPathError extends Error {
9
+ readonly code: string;
10
+ constructor(message: string, code?: string);
11
+ }
12
+ /**
13
+ * Expand simple user path forms portably:
14
+ * - `~` / `~/…` / `~\…` → home directory
15
+ * - `%VAR%` (Windows-class env expansion) when present in the string
16
+ *
17
+ * Relative paths are left relative (caller resolves against project root or cwd).
18
+ */
19
+ export declare function expandUserPath(input: string, options?: {
20
+ home?: string;
21
+ env?: NodeJS.ProcessEnv;
22
+ }): string;
23
+ /** Strip known xBRIEF suffixes to recover a write stem. */
24
+ export declare function stripXbriefSuffix(pathLike: string): string;
25
+ /**
26
+ * Resolve `--out` under `projectRoot` with user-path expansion and fail-closed
27
+ * containment. Returns absolute stem + format-specific artifact paths.
28
+ */
29
+ export declare function resolveXbriefOutPaths(input: {
30
+ projectRoot: string;
31
+ out: string;
32
+ format: XbriefFormat;
33
+ cwd?: string;
34
+ home?: string;
35
+ env?: NodeJS.ProcessEnv;
36
+ }): XbriefPaths;
37
+ //# sourceMappingURL=paths.d.ts.map