@deftai/directive 0.58.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.
- package/dist/change-init.d.ts +3 -0
- package/dist/change-init.js +59 -0
- package/dist/changelog-check.d.ts +3 -0
- package/dist/changelog-check.js +44 -0
- package/dist/cli-router/index.js +4 -1
- package/dist/cli-router/route-argv.js +2 -2
- package/dist/commit-lint.d.ts +3 -0
- package/dist/commit-lint.js +44 -0
- package/dist/dispatch.d.ts +63 -2
- package/dist/dispatch.js +1560 -22
- package/dist/doc-cli-parity.js +1 -1
- package/dist/doctor-parity.d.ts +5 -1
- package/dist/doctor-parity.js +10 -4
- package/dist/doctor.js +11 -0
- package/dist/install-cli/coverage-map.js +3 -2
- package/dist/install-uninstall.d.ts +3 -0
- package/dist/install-uninstall.js +44 -0
- package/dist/install-upgrade.d.ts +9 -0
- package/dist/install-upgrade.js +67 -0
- package/dist/migrate-preflight.d.ts +10 -0
- package/dist/migrate-preflight.js +84 -0
- package/dist/orchestration-cli/coverage-map.js +1 -1
- package/dist/session-start.d.ts +13 -0
- package/dist/session-start.js +111 -0
- package/dist/toolchain-check.d.ts +1 -1
- package/dist/toolchain-check.js +3 -2
- package/package.json +3 -3
package/dist/dispatch.js
CHANGED
|
@@ -2,9 +2,15 @@
|
|
|
2
2
|
* Unified `directive <verb> [args]` dispatcher (#1828 s0).
|
|
3
3
|
* Routes to ported command modules in packages/cli and packages/core.
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, readSync, statSync, writeFileSync, } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
7
9
|
import { engineInfo } from "@deftai/directive-core";
|
|
10
|
+
import { parseInitArgv, runInitDepositCli, userConfigDir, } from "@deftai/directive-core/init-deposit";
|
|
11
|
+
import { appendAuditLog, disclosureLine, projectDefinitionPath, resolvePolicy, resolveWipCap, setPolicy, } from "@deftai/directive-core/policy";
|
|
12
|
+
import { defaultWhich } from "@deftai/directive-core/scm";
|
|
13
|
+
import { KNOWN_SUBAGENT_BACKEND_IDS, probeSubagentBackends, resolveSwarmSubagentBackend, } from "@deftai/directive-core/swarm";
|
|
8
14
|
const HANDLER_KEYS = [
|
|
9
15
|
"run",
|
|
10
16
|
"main",
|
|
@@ -28,6 +34,12 @@ export const CLI_MODULE_VERBS = [
|
|
|
28
34
|
"codebase-projection-registry",
|
|
29
35
|
"codebase-provider",
|
|
30
36
|
"doctor",
|
|
37
|
+
"install-upgrade",
|
|
38
|
+
"install-uninstall",
|
|
39
|
+
"migrate-preflight",
|
|
40
|
+
"changelog-check",
|
|
41
|
+
"change-init",
|
|
42
|
+
"commit-lint",
|
|
31
43
|
"parity",
|
|
32
44
|
"policy",
|
|
33
45
|
"pr-closing-keywords",
|
|
@@ -43,6 +55,7 @@ export const CLI_MODULE_VERBS = [
|
|
|
43
55
|
"release-publish",
|
|
44
56
|
"release-rollback",
|
|
45
57
|
"scope-lifecycle",
|
|
58
|
+
"session-start",
|
|
46
59
|
"slice",
|
|
47
60
|
"subagent-monitor",
|
|
48
61
|
"toolchain-check",
|
|
@@ -117,6 +130,7 @@ export const CORE_MODULE_VERBS = [
|
|
|
117
130
|
"pack-migrate-patterns",
|
|
118
131
|
"pack-migrate-swarm-spec",
|
|
119
132
|
"policy-set",
|
|
133
|
+
"setup-ghx",
|
|
120
134
|
"scope-undo",
|
|
121
135
|
"scope-demote",
|
|
122
136
|
"scope-decompose",
|
|
@@ -160,7 +174,9 @@ export const VERB_ALIASES = {
|
|
|
160
174
|
"triage:accept": "triage-actions",
|
|
161
175
|
"triage:status": "triage-actions",
|
|
162
176
|
"agents:refresh": "agents-refresh",
|
|
163
|
-
"
|
|
177
|
+
"migrate:preflight": "migrate-preflight",
|
|
178
|
+
upgrade: "install-upgrade",
|
|
179
|
+
"session:start": "session-start",
|
|
164
180
|
"toolchain:check": "toolchain-check",
|
|
165
181
|
"ts:check-lane": "ts-check-lane",
|
|
166
182
|
"spec:validate": "spec-validate",
|
|
@@ -169,6 +185,7 @@ export const VERB_ALIASES = {
|
|
|
169
185
|
"project:render": "project-render",
|
|
170
186
|
doctor: "doctor",
|
|
171
187
|
build: "framework-commands",
|
|
188
|
+
"setup:ghx": "setup-ghx",
|
|
172
189
|
};
|
|
173
190
|
/** CLI modules living under verify-source-cli/ or content-validate-cli/ subdirs. */
|
|
174
191
|
const SUBDIR_CLI_STEMS = {
|
|
@@ -324,23 +341,1542 @@ function parseCodeStructureArgs(argv) {
|
|
|
324
341
|
}
|
|
325
342
|
return { projectRoot, paths, json, strict };
|
|
326
343
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
344
|
+
// ===========================================================================
|
|
345
|
+
// Native pack-migrate handlers (#2022 Phase 1).
|
|
346
|
+
//
|
|
347
|
+
// Port of scripts/pack_migrate_{skills,rules,strategies,patterns,swarm_spec}.py
|
|
348
|
+
// to native TypeScript so the pack-render surface no longer shells into bundled
|
|
349
|
+
// Python. Output parity with the Python scripts is exact, including the
|
|
350
|
+
// json.dumps(..., indent=2, ensure_ascii=True) + "\n" serialization, document
|
|
351
|
+
// scanning order, and per-entry field ordering.
|
|
352
|
+
// ===========================================================================
|
|
353
|
+
const PACK_VERSION = "0.1";
|
|
354
|
+
const DEFAULT_SKILL_VERSION = "0.1";
|
|
355
|
+
const SHOULD_NOT_GLYPH = "\u2249";
|
|
356
|
+
const MUST_NOT_GLYPH = "\u2297";
|
|
357
|
+
/** Serialize like Python json.dumps(value, indent=2, ensure_ascii=True) + "\n". */
|
|
358
|
+
function dumpsAsciiJson(value) {
|
|
359
|
+
const base = JSON.stringify(value, null, 2);
|
|
360
|
+
let out = "";
|
|
361
|
+
for (let i = 0; i < base.length; i += 1) {
|
|
362
|
+
const code = base.charCodeAt(i);
|
|
363
|
+
// ensure_ascii escapes every code unit outside the printable ASCII range
|
|
364
|
+
// (0x20-0x7e). JSON.stringify has already escaped control chars (< 0x20)
|
|
365
|
+
// and the structural quote/backslash, so only chars > 0x7e remain literal.
|
|
366
|
+
if (code > 0x7e) {
|
|
367
|
+
out += `\\u${code.toString(16).padStart(4, "0")}`;
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
out += base.charAt(i);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return `${out}\n`;
|
|
374
|
+
}
|
|
375
|
+
/** Strip leading/trailing chars in `chars` (Python str.strip(chars)); whitespace when omitted. */
|
|
376
|
+
function pyStrip(value, chars) {
|
|
377
|
+
if (chars === undefined) {
|
|
378
|
+
return value.replace(/^\s+/, "").replace(/\s+$/, "");
|
|
379
|
+
}
|
|
380
|
+
let start = 0;
|
|
381
|
+
let end = value.length;
|
|
382
|
+
while (start < end && chars.includes(value.charAt(start)))
|
|
383
|
+
start += 1;
|
|
384
|
+
while (end > start && chars.includes(value.charAt(end - 1)))
|
|
385
|
+
end -= 1;
|
|
386
|
+
return value.slice(start, end);
|
|
387
|
+
}
|
|
388
|
+
// Python str.splitlines() universal newlines: \n \r \r\n \v \f \x1c \x1d \x1e \x85 \u2028 \u2029.
|
|
389
|
+
// Built from code points (as \uXXXX escape text) so no literal control characters land in the source.
|
|
390
|
+
const LINE_BOUNDARY_CLASS = [0x0a, 0x0d, 0x0b, 0x0c, 0x1c, 0x1d, 0x1e, 0x85, 0x2028, 0x2029]
|
|
391
|
+
.map((code) => `\\u${code.toString(16).padStart(4, "0")}`)
|
|
392
|
+
.join("");
|
|
393
|
+
const LINE_BOUNDARY_RE = new RegExp(`\\r\\n|[${LINE_BOUNDARY_CLASS}]`);
|
|
394
|
+
/** Mirror Python str.splitlines(): split on universal line boundaries, dropping one terminal break. */
|
|
395
|
+
function splitLines(text) {
|
|
396
|
+
if (text === "")
|
|
397
|
+
return [];
|
|
398
|
+
const parts = text.split(LINE_BOUNDARY_RE);
|
|
399
|
+
if (parts.length > 0 && parts[parts.length - 1] === "")
|
|
400
|
+
parts.pop();
|
|
401
|
+
return parts;
|
|
402
|
+
}
|
|
403
|
+
/** Repo-relative POSIX path of `to` measured from `from`. */
|
|
404
|
+
function relPosix(from, to) {
|
|
405
|
+
return relative(from, to).split(/[\\/]/).join("/");
|
|
406
|
+
}
|
|
407
|
+
/** Python Path.stem -- filename minus its final suffix. */
|
|
408
|
+
function stemOf(filePath) {
|
|
409
|
+
const base = basename(filePath);
|
|
410
|
+
const dot = base.lastIndexOf(".");
|
|
411
|
+
return dot > 0 ? base.slice(0, dot) : base;
|
|
412
|
+
}
|
|
413
|
+
/** Slugify a doc stem: lowercase, runs of non-alnum -> '-', trimmed of '-'. */
|
|
414
|
+
function slugify(stem) {
|
|
415
|
+
return pyStrip(stem.toLowerCase().replace(/[^a-z0-9]+/g, "-"), "-");
|
|
416
|
+
}
|
|
417
|
+
function isFileSafe(path) {
|
|
418
|
+
try {
|
|
419
|
+
return statSync(path).isFile();
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function isDirSafe(path) {
|
|
426
|
+
try {
|
|
427
|
+
return statSync(path).isDirectory();
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/** Sorted SKILL.md paths one directory below skillsDir (Python skills_dir glob of the SKILL.md docs). */
|
|
434
|
+
function globSkillMd(skillsDir) {
|
|
435
|
+
const out = [];
|
|
436
|
+
let names;
|
|
437
|
+
try {
|
|
438
|
+
names = readdirSync(skillsDir);
|
|
439
|
+
}
|
|
440
|
+
catch {
|
|
441
|
+
return out;
|
|
442
|
+
}
|
|
443
|
+
for (const name of names) {
|
|
444
|
+
const dir = join(skillsDir, name);
|
|
445
|
+
if (!isDirSafe(dir))
|
|
446
|
+
continue;
|
|
447
|
+
const candidate = join(dir, "SKILL.md");
|
|
448
|
+
if (isFileSafe(candidate))
|
|
449
|
+
out.push(candidate);
|
|
450
|
+
}
|
|
451
|
+
out.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
452
|
+
return out;
|
|
453
|
+
}
|
|
454
|
+
/** Sorted full paths of `<dir>/*.md` (Python dir.glob("*.md")). */
|
|
455
|
+
function globMd(dir) {
|
|
456
|
+
const out = [];
|
|
457
|
+
let names;
|
|
458
|
+
try {
|
|
459
|
+
names = readdirSync(dir);
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
return out;
|
|
463
|
+
}
|
|
464
|
+
for (const name of names) {
|
|
465
|
+
if (!name.endsWith(".md"))
|
|
466
|
+
continue;
|
|
467
|
+
const candidate = join(dir, name);
|
|
468
|
+
if (isFileSafe(candidate))
|
|
469
|
+
out.push(candidate);
|
|
470
|
+
}
|
|
471
|
+
out.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
472
|
+
return out;
|
|
473
|
+
}
|
|
474
|
+
const H1_RE = /^#\s+(.+?)\s*$/;
|
|
475
|
+
const CHROME_PREFIXES = [
|
|
476
|
+
"legend ",
|
|
477
|
+
"legend(",
|
|
478
|
+
"**legend",
|
|
479
|
+
`**${"\u26a0\ufe0f"}`,
|
|
480
|
+
"**see also",
|
|
481
|
+
"<!--",
|
|
482
|
+
];
|
|
483
|
+
function isChrome(line) {
|
|
484
|
+
const low = line.replace(/^\s+/, "").toLowerCase();
|
|
485
|
+
if (CHROME_PREFIXES.some((prefix) => low.startsWith(prefix)))
|
|
486
|
+
return true;
|
|
487
|
+
const stripped = line.trim();
|
|
488
|
+
return stripped.length > 0 && [...stripped].every((ch) => ch === "-" || ch === "=");
|
|
489
|
+
}
|
|
490
|
+
/** Index a line array with a defined fallback (`i` is always in range at call sites). */
|
|
491
|
+
function lineAt(lines, i) {
|
|
492
|
+
return lines[i] ?? "";
|
|
493
|
+
}
|
|
494
|
+
function extractTitle(md) {
|
|
495
|
+
for (const line of splitLines(md)) {
|
|
496
|
+
const match = H1_RE.exec(line);
|
|
497
|
+
if (match)
|
|
498
|
+
return (match[1] ?? "").trim();
|
|
499
|
+
}
|
|
500
|
+
return "";
|
|
501
|
+
}
|
|
502
|
+
function extractDescription(md) {
|
|
503
|
+
const lines = splitLines(md);
|
|
504
|
+
const n = lines.length;
|
|
505
|
+
let i = 0;
|
|
506
|
+
while (i < n && !H1_RE.test(lineAt(lines, i)))
|
|
507
|
+
i += 1;
|
|
508
|
+
if (i < n)
|
|
509
|
+
i += 1;
|
|
510
|
+
while (i < n && (lineAt(lines, i).trim() === "" || isChrome(lineAt(lines, i))))
|
|
511
|
+
i += 1;
|
|
512
|
+
const block = [];
|
|
513
|
+
while (i < n &&
|
|
514
|
+
lineAt(lines, i).trim() !== "" &&
|
|
515
|
+
!lineAt(lines, i).replace(/^\s+/, "").startsWith("#")) {
|
|
516
|
+
let stripped = lineAt(lines, i).trim();
|
|
517
|
+
if (stripped.startsWith(">"))
|
|
518
|
+
stripped = stripped.replace(/^>+/, "").trim();
|
|
519
|
+
if (stripped)
|
|
520
|
+
block.push(stripped);
|
|
521
|
+
i += 1;
|
|
522
|
+
}
|
|
523
|
+
return block.join(" ");
|
|
524
|
+
}
|
|
525
|
+
const REDIRECT_MARKERS = [
|
|
526
|
+
"legacy alias",
|
|
527
|
+
"superseded",
|
|
528
|
+
"has been renamed",
|
|
529
|
+
"has moved",
|
|
530
|
+
"deprecated",
|
|
531
|
+
];
|
|
532
|
+
function isRedirectStub(md) {
|
|
533
|
+
const lines = splitLines(md);
|
|
534
|
+
const n = lines.length;
|
|
535
|
+
let i = 0;
|
|
536
|
+
while (i < n && !H1_RE.test(lineAt(lines, i)))
|
|
537
|
+
i += 1;
|
|
538
|
+
if (i < n)
|
|
539
|
+
i += 1;
|
|
540
|
+
while (i < n && (lineAt(lines, i).trim() === "" || isChrome(lineAt(lines, i))))
|
|
541
|
+
i += 1;
|
|
542
|
+
if (i >= n || !lineAt(lines, i).replace(/^\s+/, "").startsWith(">"))
|
|
543
|
+
return false;
|
|
544
|
+
const block = [];
|
|
545
|
+
while (i < n && lineAt(lines, i).replace(/^\s+/, "").startsWith(">")) {
|
|
546
|
+
block.push(lineAt(lines, i).replace(/^\s+/, "").replace(/^>+/, "").trim());
|
|
547
|
+
i += 1;
|
|
548
|
+
}
|
|
549
|
+
const quote = block.join(" ").toLowerCase();
|
|
550
|
+
return REDIRECT_MARKERS.some((marker) => quote.includes(marker));
|
|
551
|
+
}
|
|
552
|
+
const BANNER_OPEN = "<!-- AUTO-GENERATED by task packs:render";
|
|
553
|
+
function stripLeadingBanner(body) {
|
|
554
|
+
const lines = body.split("\n");
|
|
555
|
+
const n = lines.length;
|
|
556
|
+
let i = 0;
|
|
557
|
+
while (i < n && lineAt(lines, i).trim() === "")
|
|
558
|
+
i += 1;
|
|
559
|
+
if (i < n && lineAt(lines, i).startsWith(BANNER_OPEN)) {
|
|
560
|
+
while (i < n && lineAt(lines, i).replace(/^\s+/, "").startsWith("<!--"))
|
|
561
|
+
i += 1;
|
|
562
|
+
while (i < n && lineAt(lines, i).trim() === "")
|
|
563
|
+
i += 1;
|
|
564
|
+
}
|
|
565
|
+
return lines.slice(i).join("\n");
|
|
566
|
+
}
|
|
567
|
+
const FRONTMATTER_RE = /^---\n([\s\S]*?\n)---\n?([\s\S]*)$/;
|
|
568
|
+
function splitFrontmatter(text) {
|
|
569
|
+
if (!text.startsWith("---\n"))
|
|
570
|
+
return [null, text];
|
|
571
|
+
const match = FRONTMATTER_RE.exec(text);
|
|
572
|
+
if (!match)
|
|
573
|
+
return [null, text];
|
|
574
|
+
return [match[1] ?? "", match[2] ?? ""];
|
|
575
|
+
}
|
|
576
|
+
function foldBlock(blockLines) {
|
|
577
|
+
const paragraphs = [];
|
|
578
|
+
let current = [];
|
|
579
|
+
for (const line of blockLines) {
|
|
580
|
+
if (line.trim() === "") {
|
|
581
|
+
if (current.length) {
|
|
582
|
+
paragraphs.push(current.join(" "));
|
|
583
|
+
current = [];
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
else {
|
|
587
|
+
current.push(line.trim());
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (current.length)
|
|
591
|
+
paragraphs.push(current.join(" "));
|
|
592
|
+
return paragraphs.join("\n");
|
|
593
|
+
}
|
|
594
|
+
const KEY_RE = /^([A-Za-z_][\w-]*):(.*)$/;
|
|
595
|
+
const BLOCK_INDICATORS = new Set([">", ">-", ">+", "|", "|-", "|+"]);
|
|
596
|
+
function isIndented(line) {
|
|
597
|
+
return line.startsWith(" ") || line.startsWith("\t");
|
|
598
|
+
}
|
|
599
|
+
function parseFrontmatterFields(frontmatter) {
|
|
600
|
+
const lines = frontmatter.split("\n");
|
|
601
|
+
const fields = {};
|
|
602
|
+
const n = lines.length;
|
|
603
|
+
let i = 0;
|
|
604
|
+
while (i < n) {
|
|
605
|
+
const line = lineAt(lines, i);
|
|
606
|
+
const match = KEY_RE.exec(line);
|
|
607
|
+
if (!match || isIndented(line)) {
|
|
608
|
+
i += 1;
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
const key = match[1] ?? "";
|
|
612
|
+
const value = (match[2] ?? "").trim();
|
|
613
|
+
if (BLOCK_INDICATORS.has(value)) {
|
|
614
|
+
const block = [];
|
|
615
|
+
i += 1;
|
|
616
|
+
while (i < n) {
|
|
617
|
+
const nxt = lineAt(lines, i);
|
|
618
|
+
if (nxt.trim() === "") {
|
|
619
|
+
block.push("");
|
|
620
|
+
i += 1;
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
if (isIndented(nxt)) {
|
|
624
|
+
block.push(nxt);
|
|
625
|
+
i += 1;
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
break;
|
|
629
|
+
}
|
|
630
|
+
fields[key] = foldBlock(block);
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (value === "" || value.startsWith("- ")) {
|
|
634
|
+
i += 1;
|
|
635
|
+
while (i < n &&
|
|
636
|
+
(lineAt(lines, i).replace(/^\s+/, "").startsWith("- ") || isIndented(lineAt(lines, i)))) {
|
|
637
|
+
i += 1;
|
|
638
|
+
}
|
|
639
|
+
if (!(key in fields))
|
|
640
|
+
fields[key] = "";
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
fields[key] = pyStrip(pyStrip(value, '"'), "'");
|
|
644
|
+
i += 1;
|
|
645
|
+
}
|
|
646
|
+
return fields;
|
|
647
|
+
}
|
|
648
|
+
function extractExtraFrontmatter(frontmatter) {
|
|
649
|
+
const lines = frontmatter.split("\n");
|
|
650
|
+
const extra = [];
|
|
651
|
+
const n = lines.length;
|
|
652
|
+
let i = 0;
|
|
653
|
+
while (i < n) {
|
|
654
|
+
const line = lineAt(lines, i);
|
|
655
|
+
const match = KEY_RE.exec(line);
|
|
656
|
+
if (!match || isIndented(line)) {
|
|
657
|
+
i += 1;
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
const key = match[1] ?? "";
|
|
661
|
+
const value = (match[2] ?? "").trim();
|
|
662
|
+
const block = [line];
|
|
663
|
+
i += 1;
|
|
664
|
+
if (BLOCK_INDICATORS.has(value)) {
|
|
665
|
+
while (i < n && (lineAt(lines, i).trim() === "" || isIndented(lineAt(lines, i)))) {
|
|
666
|
+
block.push(lineAt(lines, i));
|
|
667
|
+
i += 1;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
else if (value === "" || value.startsWith("- ")) {
|
|
671
|
+
while (i < n &&
|
|
672
|
+
(lineAt(lines, i).replace(/^\s+/, "").startsWith("- ") || isIndented(lineAt(lines, i)))) {
|
|
673
|
+
block.push(lineAt(lines, i));
|
|
674
|
+
i += 1;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (key !== "name" && key !== "description")
|
|
678
|
+
extra.push(...block);
|
|
679
|
+
}
|
|
680
|
+
while (extra.length && (extra[extra.length - 1] ?? "").trim() === "")
|
|
681
|
+
extra.pop();
|
|
682
|
+
return extra.length ? extra.join("\n") : null;
|
|
683
|
+
}
|
|
684
|
+
const ROUTING_HEADING = "## Skill Routing";
|
|
685
|
+
const ROUTING_PATH_RE = /`(?:content\/)?(skills\/[^`]+\/SKILL\.md)`/;
|
|
686
|
+
const ARROW_SPLIT_RE = /\u2192|->/;
|
|
687
|
+
function parseRouting(agentsMd) {
|
|
688
|
+
const mapping = new Map();
|
|
689
|
+
const start = agentsMd.indexOf(ROUTING_HEADING);
|
|
690
|
+
if (start === -1)
|
|
691
|
+
return mapping;
|
|
692
|
+
const rest = agentsMd.slice(start + ROUTING_HEADING.length);
|
|
693
|
+
const end = rest.indexOf("\n## ");
|
|
694
|
+
const section = end !== -1 ? rest.slice(0, end) : rest;
|
|
695
|
+
for (const raw of splitLines(section)) {
|
|
696
|
+
const line = raw.trim();
|
|
697
|
+
if (!line.startsWith("- "))
|
|
698
|
+
continue;
|
|
699
|
+
const pathMatch = ROUTING_PATH_RE.exec(line);
|
|
700
|
+
if (!pathMatch)
|
|
701
|
+
continue;
|
|
702
|
+
const path = pathMatch[1] ?? "";
|
|
703
|
+
const head = line.split(ARROW_SPLIT_RE)[0] ?? "";
|
|
704
|
+
const keywords = (head.match(/"[^"]+"/g) ?? []).map((quoted) => quoted.slice(1, -1));
|
|
705
|
+
let bucket = mapping.get(path);
|
|
706
|
+
if (!bucket) {
|
|
707
|
+
bucket = [];
|
|
708
|
+
mapping.set(path, bucket);
|
|
709
|
+
}
|
|
710
|
+
for (const keyword of keywords) {
|
|
711
|
+
if (!bucket.includes(keyword))
|
|
712
|
+
bucket.push(keyword);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return mapping;
|
|
716
|
+
}
|
|
717
|
+
function buildSkillEntry(skillMd, skillsDir, routing, captureBody) {
|
|
718
|
+
const text = readFileSync(skillMd, "utf8");
|
|
719
|
+
const [frontmatter, body] = splitFrontmatter(text);
|
|
720
|
+
if (frontmatter === null)
|
|
721
|
+
return null;
|
|
722
|
+
const fields = parseFrontmatterFields(frontmatter);
|
|
723
|
+
const name = (fields.name ?? "").trim();
|
|
724
|
+
if (!name)
|
|
725
|
+
return null;
|
|
726
|
+
const relPath = relPosix(dirname(resolve(skillsDir)), resolve(skillMd));
|
|
727
|
+
const triggers = routing.get(relPath) ?? [];
|
|
728
|
+
const version = (fields.version ?? "").trim() || DEFAULT_SKILL_VERSION;
|
|
729
|
+
return {
|
|
730
|
+
id: name,
|
|
731
|
+
description: (fields.description ?? "").trim(),
|
|
732
|
+
triggers,
|
|
733
|
+
path: relPath,
|
|
734
|
+
version,
|
|
735
|
+
body: captureBody ? stripLeadingBanner(body) : null,
|
|
736
|
+
frontmatter_extra: extractExtraFrontmatter(frontmatter),
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
function buildSkillsPack(skillsDir, agentsMd, proofSkill) {
|
|
740
|
+
const routing = parseRouting(readFileSync(agentsMd, "utf8"));
|
|
741
|
+
const captureAll = proofSkill === null;
|
|
742
|
+
const proofPath = proofSkill !== null ? `skills/${proofSkill}/SKILL.md` : null;
|
|
743
|
+
const base = dirname(resolve(skillsDir));
|
|
744
|
+
const skills = [];
|
|
745
|
+
for (const skillMd of globSkillMd(skillsDir)) {
|
|
746
|
+
const relPath = relPosix(base, resolve(skillMd));
|
|
747
|
+
const entry = buildSkillEntry(skillMd, skillsDir, routing, captureAll || relPath === proofPath);
|
|
748
|
+
if (entry !== null)
|
|
749
|
+
skills.push(entry);
|
|
750
|
+
}
|
|
751
|
+
return {
|
|
752
|
+
pack: "skills-pack-0.1",
|
|
753
|
+
version: PACK_VERSION,
|
|
754
|
+
generated_from: "skills/*/SKILL.md + AGENTS.md (Skill Routing)",
|
|
755
|
+
skills,
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
const GLYPH_TIER = {
|
|
759
|
+
"!": "MUST",
|
|
760
|
+
"~": "SHOULD",
|
|
761
|
+
[SHOULD_NOT_GLYPH]: "SHOULD_NOT",
|
|
762
|
+
[MUST_NOT_GLYPH]: "MUST_NOT",
|
|
763
|
+
"?": "MAY",
|
|
764
|
+
};
|
|
765
|
+
const MARKER_RE = new RegExp(`^\\s*(?:-\\s+)?([!~?${SHOULD_NOT_GLYPH}${MUST_NOT_GLYPH}])\\s+(\\S.*)$`);
|
|
766
|
+
const PROSE_TIERS = [
|
|
767
|
+
["MUST NOT", "MUST_NOT"],
|
|
768
|
+
["SHOULD NOT", "SHOULD_NOT"],
|
|
769
|
+
["MUST", "MUST"],
|
|
770
|
+
["SHOULD", "SHOULD"],
|
|
771
|
+
["MAY", "MAY"],
|
|
772
|
+
];
|
|
773
|
+
function proseTier(text) {
|
|
774
|
+
for (const [keyword, tier] of PROSE_TIERS) {
|
|
775
|
+
const pattern = new RegExp(`\\b${keyword.replace(/ /g, "[ ]")}\\b`);
|
|
776
|
+
if (pattern.test(text))
|
|
777
|
+
return tier;
|
|
778
|
+
}
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
781
|
+
function parseRules(md, domain) {
|
|
782
|
+
const rules = [];
|
|
783
|
+
let seq = 0;
|
|
784
|
+
for (const raw of splitLines(md)) {
|
|
785
|
+
const line = raw.replace(/\s+$/, "");
|
|
786
|
+
let tier = null;
|
|
787
|
+
let text = "";
|
|
788
|
+
const marker = MARKER_RE.exec(line);
|
|
789
|
+
if (marker) {
|
|
790
|
+
tier = GLYPH_TIER[marker[1] ?? ""] ?? null;
|
|
791
|
+
text = (marker[2] ?? "").trim();
|
|
792
|
+
}
|
|
793
|
+
else {
|
|
794
|
+
const stripped = line.trim();
|
|
795
|
+
if (!stripped.startsWith("- "))
|
|
796
|
+
continue;
|
|
797
|
+
text = stripped.slice(2).trim();
|
|
798
|
+
tier = text ? proseTier(text) : null;
|
|
799
|
+
}
|
|
800
|
+
if (tier === null || text === "")
|
|
801
|
+
continue;
|
|
802
|
+
seq += 1;
|
|
803
|
+
rules.push({ id: `${domain}-${String(seq).padStart(3, "0")}`, tier, domain, text });
|
|
804
|
+
}
|
|
805
|
+
return rules;
|
|
806
|
+
}
|
|
807
|
+
const MANAGED_SECTION_RE = /<!--\s*deft:managed-section[\s\S]*?<!--\s*\/deft:managed-section\s*-->/g;
|
|
808
|
+
function stripManagedSection(md) {
|
|
809
|
+
return md.replace(MANAGED_SECTION_RE, "");
|
|
810
|
+
}
|
|
811
|
+
function buildRulesPack(codingDir, extraSources) {
|
|
812
|
+
const base = dirname(resolve(codingDir));
|
|
813
|
+
const rules = [];
|
|
814
|
+
for (const md of globMd(codingDir)) {
|
|
815
|
+
const relPath = relPosix(base, resolve(md));
|
|
816
|
+
const domain = slugify(stemOf(md));
|
|
817
|
+
const text = readFileSync(md, "utf8");
|
|
818
|
+
const docRules = parseRules(text, domain);
|
|
819
|
+
docRules.forEach((rule, idx) => {
|
|
820
|
+
rule.path = relPath;
|
|
821
|
+
rule.body = idx === 0 ? stripLeadingBanner(text) : null;
|
|
822
|
+
rules.push(rule);
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
for (const src of extraSources) {
|
|
826
|
+
if (!isFileSafe(src))
|
|
827
|
+
continue;
|
|
828
|
+
const candidate = relPosix(base, resolve(src));
|
|
829
|
+
const relPath = candidate.startsWith("..") || isAbsolute(candidate) ? basename(src) : candidate;
|
|
830
|
+
const domain = slugify(stemOf(src));
|
|
831
|
+
let text = readFileSync(src, "utf8");
|
|
832
|
+
if (basename(src) === "AGENTS.md")
|
|
833
|
+
text = stripManagedSection(text);
|
|
834
|
+
for (const rule of parseRules(text, domain)) {
|
|
835
|
+
rule.path = relPath;
|
|
836
|
+
rule.body = null;
|
|
837
|
+
rules.push(rule);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
return {
|
|
841
|
+
pack: "rules-pack-0.1",
|
|
842
|
+
version: PACK_VERSION,
|
|
843
|
+
generated_from: "coding/*.md + AGENTS.md + main.md (marker-prefixed RFC2119 directives; " +
|
|
844
|
+
"AGENTS.md managed-section excluded; coding bodies rendered, " +
|
|
845
|
+
"AGENTS.md/main.md metadata-only)",
|
|
846
|
+
rules,
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
function buildMdEntry(md, dir, captureBody) {
|
|
850
|
+
const relPath = relPosix(dirname(resolve(dir)), resolve(md));
|
|
851
|
+
const stemSlug = slugify(stemOf(md));
|
|
852
|
+
const text = readFileSync(md, "utf8");
|
|
853
|
+
return {
|
|
854
|
+
id: stemSlug,
|
|
855
|
+
title: extractTitle(text),
|
|
856
|
+
description: extractDescription(text),
|
|
857
|
+
triggers: stemSlug ? [stemSlug] : [],
|
|
858
|
+
path: relPath,
|
|
859
|
+
body: captureBody ? stripLeadingBanner(text) : null,
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
function buildStrategiesPack(strategiesDir, proofStrategy) {
|
|
863
|
+
const base = dirname(resolve(strategiesDir));
|
|
864
|
+
const captureAll = proofStrategy === null;
|
|
865
|
+
const strategies = [];
|
|
866
|
+
for (const md of globMd(strategiesDir)) {
|
|
867
|
+
const relPath = relPosix(base, resolve(md));
|
|
868
|
+
const captureBody = captureAll
|
|
869
|
+
? !isRedirectStub(readFileSync(md, "utf8"))
|
|
870
|
+
: relPath === proofStrategy;
|
|
871
|
+
strategies.push(buildMdEntry(md, strategiesDir, captureBody));
|
|
872
|
+
}
|
|
873
|
+
return {
|
|
874
|
+
pack: "strategies-pack-0.1",
|
|
875
|
+
version: PACK_VERSION,
|
|
876
|
+
generated_from: "strategies/*.md",
|
|
877
|
+
strategies,
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
function buildPatternsPack(patternsDir, proofPattern) {
|
|
881
|
+
const base = dirname(resolve(patternsDir));
|
|
882
|
+
const patterns = [];
|
|
883
|
+
for (const md of globMd(patternsDir)) {
|
|
884
|
+
const relPath = relPosix(base, resolve(md));
|
|
885
|
+
patterns.push(buildMdEntry(md, patternsDir, relPath === proofPattern));
|
|
886
|
+
}
|
|
887
|
+
return {
|
|
888
|
+
pack: "patterns-pack-0.1",
|
|
889
|
+
version: PACK_VERSION,
|
|
890
|
+
generated_from: "patterns/*.md",
|
|
891
|
+
patterns,
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
function buildSwarmSpecPack(swarmDir, proofEntry) {
|
|
895
|
+
const base = dirname(resolve(swarmDir));
|
|
896
|
+
const entries = [];
|
|
897
|
+
for (const md of globMd(swarmDir)) {
|
|
898
|
+
const relPath = relPosix(base, resolve(md));
|
|
899
|
+
entries.push(buildMdEntry(md, swarmDir, relPath === proofEntry));
|
|
900
|
+
}
|
|
901
|
+
return {
|
|
902
|
+
pack: "swarm-spec-pack-0.1",
|
|
903
|
+
version: PACK_VERSION,
|
|
904
|
+
generated_from: "swarm/*.md",
|
|
905
|
+
entries,
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
function writePack(out, pack) {
|
|
909
|
+
mkdirSync(dirname(out), { recursive: true });
|
|
910
|
+
writeFileSync(out, dumpsAsciiJson(pack), "utf8");
|
|
911
|
+
}
|
|
912
|
+
/** Resolve the shippable content root: <root>/content when present, else <root> (#1875). */
|
|
913
|
+
function resolveContentRoot() {
|
|
914
|
+
const root = resolveDeftRoot();
|
|
915
|
+
const candidate = join(root, "content");
|
|
916
|
+
return isDirSafe(candidate) ? candidate : root;
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Minimal argparse-compatible option reader supporting `--flag value` and
|
|
920
|
+
* `--flag=value`. `listFlags` accumulate repeats; all flags take a value.
|
|
921
|
+
*/
|
|
922
|
+
function parsePackArgs(argv, valueFlags, listFlags = []) {
|
|
923
|
+
const values = {};
|
|
924
|
+
const lists = {};
|
|
925
|
+
const known = new Set([...valueFlags, ...listFlags]);
|
|
926
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
927
|
+
const arg = argv[i];
|
|
928
|
+
if (arg === undefined)
|
|
929
|
+
continue;
|
|
930
|
+
let flag = arg;
|
|
931
|
+
let inlineValue;
|
|
932
|
+
if (arg.startsWith("--") && arg.includes("=")) {
|
|
933
|
+
const eq = arg.indexOf("=");
|
|
934
|
+
flag = arg.slice(0, eq);
|
|
935
|
+
inlineValue = arg.slice(eq + 1);
|
|
936
|
+
}
|
|
937
|
+
if (!known.has(flag)) {
|
|
938
|
+
return { values, lists, error: `unrecognized argument: ${arg}` };
|
|
939
|
+
}
|
|
940
|
+
let value = inlineValue;
|
|
941
|
+
if (value === undefined) {
|
|
942
|
+
i += 1;
|
|
943
|
+
value = argv[i];
|
|
944
|
+
}
|
|
945
|
+
if (value === undefined) {
|
|
946
|
+
return { values, lists, error: `argument ${flag}: expected one argument` };
|
|
947
|
+
}
|
|
948
|
+
if (listFlags.includes(flag)) {
|
|
949
|
+
const bucket = lists[flag] ?? [];
|
|
950
|
+
bucket.push(value);
|
|
951
|
+
lists[flag] = bucket;
|
|
952
|
+
}
|
|
953
|
+
else {
|
|
954
|
+
values[flag] = value;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
return { values, lists };
|
|
958
|
+
}
|
|
959
|
+
function runPackMigrateSkills(argv, io) {
|
|
960
|
+
const contentRoot = resolveContentRoot();
|
|
961
|
+
const parsed = parsePackArgs(argv, ["--skills-dir", "--agents-md", "--proof-skill", "--out"]);
|
|
962
|
+
if (parsed.error !== undefined) {
|
|
963
|
+
io.writeErr(`error: ${parsed.error}\n`);
|
|
964
|
+
return 2;
|
|
965
|
+
}
|
|
966
|
+
const skillsDir = parsed.values["--skills-dir"] ?? join(contentRoot, "skills");
|
|
967
|
+
const agentsMd = parsed.values["--agents-md"] ?? join(resolveDeftRoot(), "AGENTS.md");
|
|
968
|
+
const proofSkill = parsed.values["--proof-skill"] ?? null;
|
|
969
|
+
const out = parsed.values["--out"] ?? join(contentRoot, "packs", "skills", "skills-pack-0.1.json");
|
|
970
|
+
if (!isDirSafe(skillsDir)) {
|
|
971
|
+
io.writeErr(`error: skills directory not found: ${skillsDir}\n`);
|
|
972
|
+
return 1;
|
|
973
|
+
}
|
|
974
|
+
if (!isFileSafe(agentsMd)) {
|
|
975
|
+
io.writeErr(`error: AGENTS.md not found: ${agentsMd}\n`);
|
|
976
|
+
return 1;
|
|
977
|
+
}
|
|
978
|
+
const pack = buildSkillsPack(skillsDir, agentsMd, proofSkill);
|
|
979
|
+
if (pack.skills.length === 0) {
|
|
980
|
+
io.writeErr(`error: no skills with frontmatter discovered under ${skillsDir}\n`);
|
|
981
|
+
return 1;
|
|
982
|
+
}
|
|
983
|
+
writePack(out, pack);
|
|
984
|
+
const bodied = pack.skills.filter((s) => s.body !== null).length;
|
|
985
|
+
io.writeOut(`Migrated ${pack.skills.length} skills (${bodied} with body) -> ${out}\n`);
|
|
986
|
+
return 0;
|
|
987
|
+
}
|
|
988
|
+
function runPackMigrateRules(argv, io) {
|
|
989
|
+
const contentRoot = resolveContentRoot();
|
|
990
|
+
const deftRoot = resolveDeftRoot();
|
|
991
|
+
const parsed = parsePackArgs(argv, ["--coding-dir", "--out"], ["--extra-source"]);
|
|
992
|
+
if (parsed.error !== undefined) {
|
|
993
|
+
io.writeErr(`error: ${parsed.error}\n`);
|
|
994
|
+
return 2;
|
|
995
|
+
}
|
|
996
|
+
const codingDir = parsed.values["--coding-dir"] ?? join(contentRoot, "coding");
|
|
997
|
+
const extraSources = parsed.lists["--extra-source"] ?? [
|
|
998
|
+
join(deftRoot, "AGENTS.md"),
|
|
999
|
+
join(deftRoot, "main.md"),
|
|
1000
|
+
];
|
|
1001
|
+
const out = parsed.values["--out"] ?? join(contentRoot, "packs", "rules", "rules-pack-0.1.json");
|
|
1002
|
+
if (!isDirSafe(codingDir)) {
|
|
1003
|
+
io.writeErr(`error: coding directory not found: ${codingDir}\n`);
|
|
1004
|
+
return 1;
|
|
1005
|
+
}
|
|
1006
|
+
const pack = buildRulesPack(codingDir, extraSources);
|
|
1007
|
+
if (pack.rules.length === 0) {
|
|
1008
|
+
io.writeErr(`error: no directives discovered under ${codingDir}\n`);
|
|
1009
|
+
return 1;
|
|
1010
|
+
}
|
|
1011
|
+
writePack(out, pack);
|
|
1012
|
+
const bodied = pack.rules.filter((r) => r.body != null).length;
|
|
1013
|
+
io.writeOut(`Migrated ${pack.rules.length} rules (${bodied} with body) -> ${out}\n`);
|
|
1014
|
+
return 0;
|
|
1015
|
+
}
|
|
1016
|
+
function runPackMigrateStrategies(argv, io) {
|
|
1017
|
+
const contentRoot = resolveContentRoot();
|
|
1018
|
+
const parsed = parsePackArgs(argv, ["--strategies-dir", "--proof-strategy", "--out"]);
|
|
1019
|
+
if (parsed.error !== undefined) {
|
|
1020
|
+
io.writeErr(`error: ${parsed.error}\n`);
|
|
1021
|
+
return 2;
|
|
1022
|
+
}
|
|
1023
|
+
const strategiesDir = parsed.values["--strategies-dir"] ?? join(contentRoot, "strategies");
|
|
1024
|
+
const proofStrategy = parsed.values["--proof-strategy"] ?? null;
|
|
1025
|
+
const out = parsed.values["--out"] ?? join(contentRoot, "packs", "strategies", "strategies-pack-0.1.json");
|
|
1026
|
+
if (!isDirSafe(strategiesDir)) {
|
|
1027
|
+
io.writeErr(`error: strategies directory not found: ${strategiesDir}\n`);
|
|
1028
|
+
return 1;
|
|
1029
|
+
}
|
|
1030
|
+
const pack = buildStrategiesPack(strategiesDir, proofStrategy);
|
|
1031
|
+
if (pack.strategies.length === 0) {
|
|
1032
|
+
io.writeErr(`error: no strategies discovered under ${strategiesDir}\n`);
|
|
1033
|
+
return 1;
|
|
1034
|
+
}
|
|
1035
|
+
writePack(out, pack);
|
|
1036
|
+
const bodied = pack.strategies.filter((s) => s.body !== null).length;
|
|
1037
|
+
io.writeOut(`Migrated ${pack.strategies.length} strategies (${bodied} with body) -> ${out}\n`);
|
|
1038
|
+
return 0;
|
|
1039
|
+
}
|
|
1040
|
+
function runPackMigratePatterns(argv, io) {
|
|
1041
|
+
const contentRoot = resolveContentRoot();
|
|
1042
|
+
const parsed = parsePackArgs(argv, ["--patterns-dir", "--proof-pattern", "--out"]);
|
|
1043
|
+
if (parsed.error !== undefined) {
|
|
1044
|
+
io.writeErr(`error: ${parsed.error}\n`);
|
|
1045
|
+
return 2;
|
|
1046
|
+
}
|
|
1047
|
+
const patternsDir = parsed.values["--patterns-dir"] ?? join(contentRoot, "patterns");
|
|
1048
|
+
const proofPattern = parsed.values["--proof-pattern"] ?? "patterns/multi-agent.md";
|
|
1049
|
+
const out = parsed.values["--out"] ?? join(contentRoot, "packs", "patterns", "patterns-pack-0.1.json");
|
|
1050
|
+
if (!isDirSafe(patternsDir)) {
|
|
1051
|
+
io.writeErr(`error: patterns directory not found: ${patternsDir}\n`);
|
|
1052
|
+
return 1;
|
|
1053
|
+
}
|
|
1054
|
+
const pack = buildPatternsPack(patternsDir, proofPattern);
|
|
1055
|
+
if (pack.patterns.length === 0) {
|
|
1056
|
+
io.writeErr(`error: no patterns discovered under ${patternsDir}\n`);
|
|
1057
|
+
return 1;
|
|
1058
|
+
}
|
|
1059
|
+
writePack(out, pack);
|
|
1060
|
+
const bodied = pack.patterns.filter((p) => p.body !== null).length;
|
|
1061
|
+
io.writeOut(`Migrated ${pack.patterns.length} patterns (${bodied} with body) -> ${out}\n`);
|
|
1062
|
+
return 0;
|
|
1063
|
+
}
|
|
1064
|
+
function runPackMigrateSwarmSpec(argv, io) {
|
|
1065
|
+
const contentRoot = resolveContentRoot();
|
|
1066
|
+
const parsed = parsePackArgs(argv, ["--swarm-dir", "--proof-entry", "--out"]);
|
|
1067
|
+
if (parsed.error !== undefined) {
|
|
1068
|
+
io.writeErr(`error: ${parsed.error}\n`);
|
|
1069
|
+
return 2;
|
|
1070
|
+
}
|
|
1071
|
+
const swarmDir = parsed.values["--swarm-dir"] ?? join(contentRoot, "swarm");
|
|
1072
|
+
const proofEntry = parsed.values["--proof-entry"] ?? "swarm/swarm.md";
|
|
1073
|
+
const out = parsed.values["--out"] ?? join(contentRoot, "packs", "swarm-spec", "swarm-spec-pack-0.1.json");
|
|
1074
|
+
if (!isDirSafe(swarmDir)) {
|
|
1075
|
+
io.writeErr(`error: swarm directory not found: ${swarmDir}\n`);
|
|
1076
|
+
return 1;
|
|
1077
|
+
}
|
|
1078
|
+
const pack = buildSwarmSpecPack(swarmDir, proofEntry);
|
|
1079
|
+
if (pack.entries.length === 0) {
|
|
1080
|
+
io.writeErr(`error: no swarm-spec docs discovered under ${swarmDir}\n`);
|
|
1081
|
+
return 1;
|
|
1082
|
+
}
|
|
1083
|
+
writePack(out, pack);
|
|
1084
|
+
const bodied = pack.entries.filter((e) => e.body !== null).length;
|
|
1085
|
+
io.writeOut(`Migrated ${pack.entries.length} swarm-spec entries (${bodied} with body) -> ${out}\n`);
|
|
1086
|
+
return 0;
|
|
1087
|
+
}
|
|
1088
|
+
// ===========================================================================
|
|
1089
|
+
// Native setup:ghx handler (#2022 Phase 1).
|
|
1090
|
+
//
|
|
1091
|
+
// Port of scripts/setup_ghx.py to native TypeScript: consent-gated ghx proxy
|
|
1092
|
+
// installer with three-state exit (0 ok / 1 install failure / 2 config error).
|
|
1093
|
+
// ===========================================================================
|
|
1094
|
+
/** Pinned ghx version — keep in lockstep with .github/workflows/ci.yml env.GHX_VERSION. */
|
|
1095
|
+
export const GHX_VERSION = "v1.5.1";
|
|
1096
|
+
export const INSTALL_PS1_URL = `https://raw.githubusercontent.com/brunoborges/ghx/${GHX_VERSION}/install.ps1`;
|
|
1097
|
+
export const INSTALL_SH_URL = `https://raw.githubusercontent.com/brunoborges/ghx/${GHX_VERSION}/install.sh`;
|
|
1098
|
+
function parseSetupGhxArgs(argv) {
|
|
1099
|
+
let yes = false;
|
|
1100
|
+
let check = false;
|
|
1101
|
+
for (const arg of argv) {
|
|
1102
|
+
if (arg === "--yes") {
|
|
1103
|
+
yes = true;
|
|
1104
|
+
}
|
|
1105
|
+
else if (arg === "--check") {
|
|
1106
|
+
check = true;
|
|
1107
|
+
}
|
|
1108
|
+
else {
|
|
1109
|
+
return { yes, check, error: `unrecognized argument: ${arg}` };
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
return { yes, check };
|
|
1113
|
+
}
|
|
1114
|
+
export function ghxPresent(whichFn = defaultWhich) {
|
|
1115
|
+
return whichFn("ghx") !== null;
|
|
1116
|
+
}
|
|
1117
|
+
export function detectSetupGhxHost() {
|
|
1118
|
+
if (process.platform === "win32")
|
|
1119
|
+
return "windows";
|
|
1120
|
+
if (process.platform === "darwin")
|
|
1121
|
+
return "darwin";
|
|
1122
|
+
if (process.platform === "linux")
|
|
1123
|
+
return "linux";
|
|
1124
|
+
return process.platform;
|
|
1125
|
+
}
|
|
1126
|
+
function readConsentLineFromStdin() {
|
|
1127
|
+
const buf = Buffer.alloc(256);
|
|
1128
|
+
try {
|
|
1129
|
+
const n = readSync(0, buf, 0, 256, null);
|
|
1130
|
+
if (n === null || n <= 0)
|
|
1131
|
+
return "";
|
|
1132
|
+
return buf.toString("utf8", 0, n);
|
|
1133
|
+
}
|
|
1134
|
+
catch {
|
|
1135
|
+
return "";
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
export function promptSetupGhxConsent(io, readLine = readConsentLineFromStdin) {
|
|
1139
|
+
io.writeOut("\n[setup_ghx] ghx is the recommended GitHub CLI cache proxy for deft " +
|
|
1140
|
+
"maintainers (prevents rate-limiting in multi-agent swarms; speeds up " +
|
|
1141
|
+
"scm:* calls). Consumer projects only require gh.\n");
|
|
1142
|
+
io.writeOut(`[setup_ghx] Upstream: https://github.com/brunoborges/ghx (${GHX_VERSION})\n`);
|
|
1143
|
+
io.writeOut("[setup_ghx] Install ghx via the upstream installer? [y/N]: ");
|
|
1144
|
+
const answer = readLine().trim().toLowerCase();
|
|
1145
|
+
return answer === "y" || answer === "yes";
|
|
1146
|
+
}
|
|
1147
|
+
export function buildSetupGhxInstallCommand(host, whichFn = defaultWhich) {
|
|
1148
|
+
if (host === "windows") {
|
|
1149
|
+
const psBin = whichFn("pwsh") ?? whichFn("powershell") ?? "powershell";
|
|
1150
|
+
return [
|
|
1151
|
+
psBin,
|
|
1152
|
+
"-NoProfile",
|
|
1153
|
+
"-ExecutionPolicy",
|
|
1154
|
+
"Bypass",
|
|
1155
|
+
"-Command",
|
|
1156
|
+
`irm ${INSTALL_PS1_URL} | iex`,
|
|
1157
|
+
];
|
|
1158
|
+
}
|
|
1159
|
+
if (host === "darwin" || host === "linux") {
|
|
1160
|
+
return ["bash", "-c", `curl -fsSL ${INSTALL_SH_URL} | bash`];
|
|
1161
|
+
}
|
|
1162
|
+
throw new Error(`no upstream ghx installer available for host '${host}'; ` +
|
|
1163
|
+
"see https://github.com/brunoborges/ghx#install for manual options");
|
|
1164
|
+
}
|
|
1165
|
+
export function installSetupGhx(host, whichFn = defaultWhich, runner = spawnSync) {
|
|
1166
|
+
const cmd = buildSetupGhxInstallCommand(host, whichFn);
|
|
1167
|
+
const proc = runner(cmd[0] ?? "", cmd.slice(1), {
|
|
1168
|
+
env: { ...process.env, GHX_VERSION },
|
|
1169
|
+
stdio: "inherit",
|
|
1170
|
+
});
|
|
1171
|
+
return proc.status ?? 1;
|
|
1172
|
+
}
|
|
1173
|
+
/** Native `setup:ghx` handler (replaces scripts/setup_ghx.py shell-out, #2022 Phase 1). */
|
|
1174
|
+
export function runSetupGhx(argv, io, deps = {}) {
|
|
1175
|
+
const args = parseSetupGhxArgs(argv);
|
|
1176
|
+
if (args.error !== undefined) {
|
|
1177
|
+
io.writeErr(`setup-ghx: ${args.error}\n`);
|
|
1178
|
+
return 2;
|
|
1179
|
+
}
|
|
1180
|
+
if (args.yes && args.check) {
|
|
1181
|
+
io.writeErr("[setup_ghx] error: --yes and --check are mutually exclusive.\n");
|
|
1182
|
+
return 2;
|
|
1183
|
+
}
|
|
1184
|
+
const whichFn = deps.whichFn ?? defaultWhich;
|
|
1185
|
+
if (ghxPresent(whichFn)) {
|
|
1186
|
+
io.writeOut("[setup_ghx] ghx already on PATH -- skipping install.\n");
|
|
1187
|
+
return 0;
|
|
1188
|
+
}
|
|
1189
|
+
if (args.check) {
|
|
1190
|
+
if (whichFn("gh") !== null) {
|
|
1191
|
+
io.writeOut("[setup_ghx] gh is on PATH but ghx is not; ghx is the recommended GitHub CLI " +
|
|
1192
|
+
"cache proxy. Install with `directive setup:ghx` (or `task setup:ghx`). Refs #884.\n");
|
|
1193
|
+
}
|
|
1194
|
+
else {
|
|
1195
|
+
io.writeOut("[setup_ghx] ghx not on PATH; recommended for speed -- run `directive setup:ghx` " +
|
|
1196
|
+
"to opt in. Consumer projects only require gh. Refs #884.\n");
|
|
1197
|
+
}
|
|
1198
|
+
return 0;
|
|
1199
|
+
}
|
|
1200
|
+
let consent;
|
|
1201
|
+
if (args.yes) {
|
|
1202
|
+
consent = true;
|
|
1203
|
+
io.writeOut("[setup_ghx] --yes provided; skipping interactive consent prompt.\n");
|
|
1204
|
+
}
|
|
1205
|
+
else {
|
|
1206
|
+
const skip = (process.env.DEFT_SETUP_GHX_SKIP ?? "").trim();
|
|
1207
|
+
if (skip === "1" || skip.toLowerCase() === "true" || skip.toLowerCase() === "yes") {
|
|
1208
|
+
io.writeOut("[setup_ghx] DEFT_SETUP_GHX_SKIP set; skipping ghx install. Refs #884.\n");
|
|
337
1209
|
return 0;
|
|
338
1210
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
1211
|
+
const readLine = deps.readConsentLine ?? readConsentLineFromStdin;
|
|
1212
|
+
consent = promptSetupGhxConsent(io, readLine);
|
|
1213
|
+
}
|
|
1214
|
+
if (!consent) {
|
|
1215
|
+
io.writeOut("[setup_ghx] Skipping ghx install. ghx is recommended for speed for maintainers and " +
|
|
1216
|
+
"swarm runs; consumer projects only require gh " +
|
|
1217
|
+
"(see https://github.com/brunoborges/ghx, #884).\n");
|
|
1218
|
+
return 0;
|
|
1219
|
+
}
|
|
1220
|
+
const host = detectSetupGhxHost();
|
|
1221
|
+
const runInstall = deps.runInstall ?? ((h) => installSetupGhx(h, whichFn));
|
|
1222
|
+
try {
|
|
1223
|
+
const rc = runInstall(host);
|
|
1224
|
+
if (rc !== 0) {
|
|
1225
|
+
io.writeErr(`[setup_ghx] error: upstream installer exited ${rc}. ` +
|
|
1226
|
+
"See https://github.com/brunoborges/ghx#install for manual options.\n");
|
|
1227
|
+
return 1;
|
|
1228
|
+
}
|
|
1229
|
+
io.writeOut("[setup_ghx] ghx installed. Open a fresh shell so the updated PATH takes effect, " +
|
|
1230
|
+
"then re-run `task setup` to verify.\n");
|
|
1231
|
+
return 0;
|
|
1232
|
+
}
|
|
1233
|
+
catch (err) {
|
|
1234
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1235
|
+
io.writeErr(`[setup_ghx] error: ${message}\n`);
|
|
1236
|
+
return 1;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
// ===========================================================================
|
|
1240
|
+
// Native directive bootstrap launcher (#2022 Phase 4).
|
|
1241
|
+
//
|
|
1242
|
+
// Thin operator entry: deposit-if-absent, carry phase intent + deliberate
|
|
1243
|
+
// re-entry signal, then hand off to deft-directive-setup (agent-driven).
|
|
1244
|
+
// ===========================================================================
|
|
1245
|
+
export const SETUP_SKILL_REL_PATH = ".deft/core/skills/deft-directive-setup/SKILL.md";
|
|
1246
|
+
function bootstrapPhaseLabel(phase) {
|
|
1247
|
+
if (phase === 1)
|
|
1248
|
+
return "user";
|
|
1249
|
+
if (phase === 2)
|
|
1250
|
+
return "project";
|
|
1251
|
+
return "spec";
|
|
1252
|
+
}
|
|
1253
|
+
function resolveBootstrapUserMdPath() {
|
|
1254
|
+
const override = process.env.DEFT_USER_PATH?.trim();
|
|
1255
|
+
if (override)
|
|
1256
|
+
return resolve(override);
|
|
1257
|
+
return join(userConfigDir(), "USER.md");
|
|
1258
|
+
}
|
|
1259
|
+
function defaultBootstrapDeps() {
|
|
1260
|
+
return {
|
|
1261
|
+
deftCorePresent: (projectRoot) => isDirSafe(join(resolve(projectRoot), ".deft", "core")),
|
|
1262
|
+
userMdPresent: () => isFileSafe(resolveBootstrapUserMdPath()),
|
|
1263
|
+
projectDefPresent: (projectRoot) => isFileSafe(projectDefinitionPath(projectRoot)),
|
|
1264
|
+
runInitDeposit: async (projectRoot, io) => {
|
|
1265
|
+
const initArgs = parseInitArgv(["--yes", "--repo-root", projectRoot, "--json"], []);
|
|
1266
|
+
return runInitDepositCli({
|
|
1267
|
+
...initArgs,
|
|
1268
|
+
writeOut: io.writeOut,
|
|
1269
|
+
writeErr: io.writeErr,
|
|
1270
|
+
});
|
|
1271
|
+
},
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
/** Parse `directive bootstrap` argv (#2022 Phase 4). */
|
|
1275
|
+
export function parseDirectiveBootstrapArgs(argv) {
|
|
1276
|
+
let projectRoot = ".";
|
|
1277
|
+
let jumpProject = false;
|
|
1278
|
+
let strategy = null;
|
|
1279
|
+
let reconfigure = false;
|
|
1280
|
+
let force = false;
|
|
1281
|
+
let json = false;
|
|
1282
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
1283
|
+
const arg = argv[i];
|
|
1284
|
+
if (arg === "--project") {
|
|
1285
|
+
jumpProject = true;
|
|
1286
|
+
}
|
|
1287
|
+
else if (arg === "--reconfigure") {
|
|
1288
|
+
reconfigure = true;
|
|
1289
|
+
}
|
|
1290
|
+
else if (arg === "--force") {
|
|
1291
|
+
force = true;
|
|
1292
|
+
}
|
|
1293
|
+
else if (arg === "--json") {
|
|
1294
|
+
json = true;
|
|
1295
|
+
}
|
|
1296
|
+
else if (arg === "--project-root") {
|
|
1297
|
+
const value = argv[i + 1];
|
|
1298
|
+
if (value === undefined) {
|
|
1299
|
+
return {
|
|
1300
|
+
projectRoot,
|
|
1301
|
+
jumpProject,
|
|
1302
|
+
strategy,
|
|
1303
|
+
reconfigure,
|
|
1304
|
+
force,
|
|
1305
|
+
json,
|
|
1306
|
+
error: "argument --project-root: expected one argument",
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
projectRoot = value;
|
|
1310
|
+
i += 1;
|
|
1311
|
+
}
|
|
1312
|
+
else if (arg?.startsWith("--project-root=")) {
|
|
1313
|
+
projectRoot = arg.slice("--project-root=".length);
|
|
1314
|
+
}
|
|
1315
|
+
else if (arg === "--strategy") {
|
|
1316
|
+
const value = argv[i + 1];
|
|
1317
|
+
if (value === undefined) {
|
|
1318
|
+
return {
|
|
1319
|
+
projectRoot,
|
|
1320
|
+
jumpProject,
|
|
1321
|
+
strategy,
|
|
1322
|
+
reconfigure,
|
|
1323
|
+
force,
|
|
1324
|
+
json,
|
|
1325
|
+
error: "argument --strategy: expected one argument",
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
strategy = value;
|
|
1329
|
+
i += 1;
|
|
1330
|
+
}
|
|
1331
|
+
else if (arg?.startsWith("--strategy=")) {
|
|
1332
|
+
strategy = arg.slice("--strategy=".length);
|
|
1333
|
+
}
|
|
1334
|
+
else if (arg === "--help" || arg === "-h") {
|
|
1335
|
+
return {
|
|
1336
|
+
projectRoot,
|
|
1337
|
+
jumpProject,
|
|
1338
|
+
strategy,
|
|
1339
|
+
reconfigure,
|
|
1340
|
+
force,
|
|
1341
|
+
json,
|
|
1342
|
+
error: "__help__",
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
else {
|
|
1346
|
+
return {
|
|
1347
|
+
projectRoot,
|
|
1348
|
+
jumpProject,
|
|
1349
|
+
strategy,
|
|
1350
|
+
reconfigure,
|
|
1351
|
+
force,
|
|
1352
|
+
json,
|
|
1353
|
+
error: `unrecognized argument: ${arg}`,
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
return {
|
|
1358
|
+
projectRoot: resolve(projectRoot),
|
|
1359
|
+
jumpProject,
|
|
1360
|
+
strategy,
|
|
1361
|
+
reconfigure,
|
|
1362
|
+
force,
|
|
1363
|
+
json,
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
/** Resolve phase intent and deliberate re-entry signal from parsed args + on-disk state. */
|
|
1367
|
+
export function resolveDirectiveBootstrapPlan(args, deps) {
|
|
1368
|
+
let phase;
|
|
1369
|
+
if (args.jumpProject) {
|
|
1370
|
+
phase = 2;
|
|
1371
|
+
}
|
|
1372
|
+
else if (args.strategy !== null) {
|
|
1373
|
+
phase = 3;
|
|
1374
|
+
}
|
|
1375
|
+
else if (!deps.userMdPresent(args.projectRoot)) {
|
|
1376
|
+
phase = 1;
|
|
1377
|
+
}
|
|
1378
|
+
else if (!deps.projectDefPresent(args.projectRoot)) {
|
|
1379
|
+
phase = 2;
|
|
1380
|
+
}
|
|
1381
|
+
else {
|
|
1382
|
+
phase = 3;
|
|
1383
|
+
}
|
|
1384
|
+
let reEntry = "none";
|
|
1385
|
+
if (args.force) {
|
|
1386
|
+
reEntry = "force";
|
|
1387
|
+
}
|
|
1388
|
+
else if (args.reconfigure) {
|
|
1389
|
+
reEntry = "reconfigure";
|
|
1390
|
+
}
|
|
1391
|
+
else {
|
|
1392
|
+
const artifactExists = phase === 1
|
|
1393
|
+
? deps.userMdPresent(args.projectRoot)
|
|
1394
|
+
: phase === 2
|
|
1395
|
+
? deps.projectDefPresent(args.projectRoot)
|
|
1396
|
+
: deps.projectDefPresent(args.projectRoot);
|
|
1397
|
+
if (artifactExists) {
|
|
1398
|
+
reEntry = "prompt";
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
return {
|
|
1402
|
+
phase,
|
|
1403
|
+
phaseLabel: bootstrapPhaseLabel(phase),
|
|
1404
|
+
reEntry,
|
|
1405
|
+
strategy: args.strategy,
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
function printDirectiveBootstrapHelp(io) {
|
|
1409
|
+
io.writeOut("Usage: directive bootstrap [--project-root <path>] [--project] [--strategy <name>] [--reconfigure] [--force] [--json]\n\n" +
|
|
1410
|
+
"Deposit the framework when absent, then hand off to deft-directive-setup.\n\n" +
|
|
1411
|
+
" --project Jump to Phase 2 (project configuration)\n" +
|
|
1412
|
+
" --strategy Jump to Phase 3 (scope vBRIEF / spec interview)\n" +
|
|
1413
|
+
" --reconfigure Deliberate re-entry — agent should reconfigure existing artifacts\n" +
|
|
1414
|
+
" --force Skip reconfigure-or-keep prompt; overwrite allowed\n" +
|
|
1415
|
+
" --json Emit structured handoff JSON on stdout\n");
|
|
1416
|
+
}
|
|
1417
|
+
function emitBootstrapHandoff(handoff, io, json) {
|
|
1418
|
+
if (json) {
|
|
1419
|
+
io.writeOut(`${JSON.stringify(handoff, null, 2)}\n`);
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
io.writeOut("[directive bootstrap] Setup launcher — hand off to deft-directive-setup\n\n");
|
|
1423
|
+
io.writeOut(`project_root: ${handoff.project_root}\n`);
|
|
1424
|
+
io.writeOut(`deposited: ${handoff.deposited}\n`);
|
|
1425
|
+
io.writeOut(`phase: ${handoff.phase} (${handoff.phase_label})\n`);
|
|
1426
|
+
io.writeOut(`re_entry: ${handoff.re_entry}\n`);
|
|
1427
|
+
if (handoff.strategy !== null) {
|
|
1428
|
+
io.writeOut(`strategy: ${handoff.strategy}\n`);
|
|
1429
|
+
}
|
|
1430
|
+
io.writeOut(`\nNext: Read and follow ${handoff.skill_path}\n`);
|
|
1431
|
+
}
|
|
1432
|
+
/** Native `directive bootstrap` handler (#2022 Phase 4). */
|
|
1433
|
+
export async function runDirectiveBootstrap(argv, io, deps = {}) {
|
|
1434
|
+
const merged = { ...defaultBootstrapDeps(), ...deps };
|
|
1435
|
+
const args = parseDirectiveBootstrapArgs(argv);
|
|
1436
|
+
if (args.error === "__help__") {
|
|
1437
|
+
printDirectiveBootstrapHelp(io);
|
|
1438
|
+
return 0;
|
|
1439
|
+
}
|
|
1440
|
+
if (args.error !== undefined) {
|
|
1441
|
+
io.writeErr(`directive bootstrap: ${args.error}\n`);
|
|
1442
|
+
return 2;
|
|
1443
|
+
}
|
|
1444
|
+
const plan = resolveDirectiveBootstrapPlan(args, merged);
|
|
1445
|
+
let deposited = false;
|
|
1446
|
+
if (!merged.deftCorePresent(args.projectRoot)) {
|
|
1447
|
+
const initCode = await merged.runInitDeposit(args.projectRoot, io);
|
|
1448
|
+
if (initCode !== 0) {
|
|
1449
|
+
return initCode;
|
|
1450
|
+
}
|
|
1451
|
+
deposited = true;
|
|
1452
|
+
}
|
|
1453
|
+
const handoff = {
|
|
1454
|
+
handoff: "deft-directive-setup",
|
|
1455
|
+
skill_path: SETUP_SKILL_REL_PATH,
|
|
1456
|
+
project_root: args.projectRoot,
|
|
1457
|
+
deposited,
|
|
1458
|
+
phase: plan.phase,
|
|
1459
|
+
phase_label: plan.phaseLabel,
|
|
1460
|
+
re_entry: plan.reEntry,
|
|
1461
|
+
strategy: plan.strategy,
|
|
1462
|
+
};
|
|
1463
|
+
emitBootstrapHandoff(handoff, io, args.json);
|
|
1464
|
+
return 0;
|
|
1465
|
+
}
|
|
1466
|
+
// ===========================================================================
|
|
1467
|
+
// Native policy-set handler (#2022 Phase 1).
|
|
1468
|
+
//
|
|
1469
|
+
// Port of scripts/policy_set.py to native TypeScript so the typed-policy write
|
|
1470
|
+
// path (enforce-branches / allow-direct-commits / wip-cap / subagent-backend)
|
|
1471
|
+
// and the subagent-backends probe surface no longer shell into bundled Python.
|
|
1472
|
+
// Behaviour parity with the Python script is preserved: the audit row appended
|
|
1473
|
+
// to meta/policy-changes.log, the json.dumps(..., indent=2, ensure_ascii=False)
|
|
1474
|
+
// + "\n" serialization, the disclosure text, and the exit codes
|
|
1475
|
+
// (0 success / 1 refusal / 2 config-or-parse error).
|
|
1476
|
+
// ===========================================================================
|
|
1477
|
+
const POLICY_CAPABILITY_COST_DISCLOSURE = "\u26a0 Capability-cost disclosure -- enabling direct commits to the default " +
|
|
1478
|
+
"branch turns OFF the deft branch-protection policy.\n" +
|
|
1479
|
+
" \u2022 Pre-commit + pre-push hooks will no longer block default-branch " +
|
|
1480
|
+
"commits.\n" +
|
|
1481
|
+
" \u2022 verify:branch will pass on the default branch.\n" +
|
|
1482
|
+
" \u2022 The CI sanity check (head_ref != base_ref) is still independent and " +
|
|
1483
|
+
"will continue to flag master->master PRs.\n" +
|
|
1484
|
+
" \u2022 This change is reversible: run `task policy:enforce-branches` to " +
|
|
1485
|
+
"re-enable the gate.\n" +
|
|
1486
|
+
" \u2022 The change is recorded to meta/policy-changes.log for auditability.";
|
|
1487
|
+
const POLICY_WIP_CAP_DISCLOSURE = "\u26a0 Capability-cost disclosure -- changing plan.policy.wipCap " +
|
|
1488
|
+
"alters the refusal threshold on task scope:promote (#1124 / D4 of #1119).\n" +
|
|
1489
|
+
" \u2022 Raising the cap lets more vBRIEFs sit in pending/+active/ " +
|
|
1490
|
+
"before promotion is refused.\n" +
|
|
1491
|
+
" \u2022 Lowering the cap may put the project over cap immediately; " +
|
|
1492
|
+
"use `task scope:demote` / `task scope:demote --batch --older-than-days 30` " +
|
|
1493
|
+
"to drain.\n" +
|
|
1494
|
+
" \u2022 cap=0 freezes promotion entirely (useful for code-freeze " +
|
|
1495
|
+
"windows; restore by setting a positive value).\n" +
|
|
1496
|
+
" \u2022 This change is reversible and recorded to " +
|
|
1497
|
+
"meta/policy-changes.log for auditability.";
|
|
1498
|
+
const POLICY_SET_COMMANDS = [
|
|
1499
|
+
"enforce-branches",
|
|
1500
|
+
"allow-direct-commits",
|
|
1501
|
+
"wip-cap",
|
|
1502
|
+
"subagent-backend",
|
|
1503
|
+
"subagent-backends",
|
|
1504
|
+
];
|
|
1505
|
+
/** Flags each subcommand accepts (mirrors the policy_set.py argparse subparsers). */
|
|
1506
|
+
const POLICY_SET_ALLOWED_FLAGS = {
|
|
1507
|
+
"enforce-branches": new Set(["--actor", "--note", "--project-root"]),
|
|
1508
|
+
"allow-direct-commits": new Set(["--confirm", "--actor", "--note", "--project-root"]),
|
|
1509
|
+
"wip-cap": new Set(["--set", "--confirm", "--actor", "--note", "--project-root"]),
|
|
1510
|
+
"subagent-backend": new Set(["--set", "--actor", "--note", "--project-root"]),
|
|
1511
|
+
"subagent-backends": new Set(["--format", "--project-root"]),
|
|
1512
|
+
};
|
|
1513
|
+
/** Custom error so write helpers can distinguish a missing file from a config fault. */
|
|
1514
|
+
class PolicySetError extends Error {
|
|
1515
|
+
kind;
|
|
1516
|
+
constructor(message, kind) {
|
|
1517
|
+
super(message);
|
|
1518
|
+
this.name = "PolicySetError";
|
|
1519
|
+
this.kind = kind;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
/** Python repr() for the audit-trail `previous=` field (None / 'str' / int / bool). */
|
|
1523
|
+
function pyRepr(value) {
|
|
1524
|
+
if (value === undefined || value === null)
|
|
1525
|
+
return "None";
|
|
1526
|
+
if (typeof value === "string")
|
|
1527
|
+
return `'${value}'`;
|
|
1528
|
+
if (typeof value === "boolean")
|
|
1529
|
+
return value ? "True" : "False";
|
|
1530
|
+
return String(value);
|
|
1531
|
+
}
|
|
1532
|
+
/** Strip newlines so an audit note stays a single log line (mirrors policy_set.py). */
|
|
1533
|
+
function sanitizeNote(note) {
|
|
1534
|
+
return note.replace(/\n/g, " ").replace(/\r/g, " ");
|
|
1535
|
+
}
|
|
1536
|
+
function defaultPolicySetActor(cmd) {
|
|
1537
|
+
switch (cmd) {
|
|
1538
|
+
case "enforce-branches":
|
|
1539
|
+
return "task policy:enforce-branches";
|
|
1540
|
+
case "allow-direct-commits":
|
|
1541
|
+
return "task policy:allow-direct-commits";
|
|
1542
|
+
case "wip-cap":
|
|
1543
|
+
return "task policy:wip-cap";
|
|
1544
|
+
case "subagent-backend":
|
|
1545
|
+
return "task policy:subagent-backend";
|
|
1546
|
+
case "subagent-backends":
|
|
1547
|
+
return "task policy:subagent-backends";
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
/** Mirror Python `Path(...).expanduser()` for a leading `~` / `~/` segment. */
|
|
1551
|
+
function expandUser(p) {
|
|
1552
|
+
if (p === "~")
|
|
1553
|
+
return homedir();
|
|
1554
|
+
if (p.startsWith("~/") || p.startsWith("~\\")) {
|
|
1555
|
+
return join(homedir(), p.slice(2));
|
|
1556
|
+
}
|
|
1557
|
+
return p;
|
|
1558
|
+
}
|
|
1559
|
+
function policySetError(message) {
|
|
1560
|
+
return {
|
|
1561
|
+
cmd: "enforce-branches",
|
|
1562
|
+
confirm: false,
|
|
1563
|
+
actor: "",
|
|
1564
|
+
note: "",
|
|
1565
|
+
projectRoot: ".",
|
|
1566
|
+
format: "text",
|
|
1567
|
+
error: message,
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
/** Parse `policy-set <cmd> [flags]` (mirrors the policy_set.py argparse surface). */
|
|
1571
|
+
function parsePolicySetArgs(argv) {
|
|
1572
|
+
const cmd = argv[0];
|
|
1573
|
+
if (cmd === undefined) {
|
|
1574
|
+
return policySetError("the following arguments are required: cmd");
|
|
1575
|
+
}
|
|
1576
|
+
if (!POLICY_SET_COMMANDS.includes(cmd)) {
|
|
1577
|
+
return policySetError(`argument cmd: invalid choice: '${cmd}'`);
|
|
1578
|
+
}
|
|
1579
|
+
const command = cmd;
|
|
1580
|
+
const allowed = POLICY_SET_ALLOWED_FLAGS[command];
|
|
1581
|
+
const args = {
|
|
1582
|
+
cmd: command,
|
|
1583
|
+
confirm: false,
|
|
1584
|
+
actor: defaultPolicySetActor(command),
|
|
1585
|
+
note: "",
|
|
1586
|
+
projectRoot: ".",
|
|
1587
|
+
format: "text",
|
|
1588
|
+
};
|
|
1589
|
+
const rest = argv.slice(1);
|
|
1590
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
1591
|
+
const token = rest[i];
|
|
1592
|
+
if (token === undefined)
|
|
1593
|
+
continue;
|
|
1594
|
+
let flag = token;
|
|
1595
|
+
let inlineValue;
|
|
1596
|
+
if (token.startsWith("--") && token.includes("=")) {
|
|
1597
|
+
const eq = token.indexOf("=");
|
|
1598
|
+
flag = token.slice(0, eq);
|
|
1599
|
+
inlineValue = token.slice(eq + 1);
|
|
1600
|
+
}
|
|
1601
|
+
if (!allowed.has(flag)) {
|
|
1602
|
+
return policySetError(`unrecognized arguments: ${token}`);
|
|
1603
|
+
}
|
|
1604
|
+
const takeValue = () => {
|
|
1605
|
+
if (inlineValue !== undefined)
|
|
1606
|
+
return inlineValue;
|
|
1607
|
+
i += 1;
|
|
1608
|
+
return rest[i];
|
|
1609
|
+
};
|
|
1610
|
+
if (flag === "--confirm") {
|
|
1611
|
+
args.confirm = true;
|
|
1612
|
+
continue;
|
|
1613
|
+
}
|
|
1614
|
+
const value = takeValue();
|
|
1615
|
+
if (value === undefined) {
|
|
1616
|
+
return policySetError(`argument ${flag}: expected one argument`);
|
|
1617
|
+
}
|
|
1618
|
+
if (flag === "--actor") {
|
|
1619
|
+
args.actor = value;
|
|
342
1620
|
}
|
|
1621
|
+
else if (flag === "--note") {
|
|
1622
|
+
args.note = value;
|
|
1623
|
+
}
|
|
1624
|
+
else if (flag === "--project-root") {
|
|
1625
|
+
args.projectRoot = expandUser(value);
|
|
1626
|
+
}
|
|
1627
|
+
else if (flag === "--format") {
|
|
1628
|
+
if (value !== "text" && value !== "json") {
|
|
1629
|
+
return policySetError(`argument --format: invalid choice: '${value}'`);
|
|
1630
|
+
}
|
|
1631
|
+
args.format = value;
|
|
1632
|
+
}
|
|
1633
|
+
else if (flag === "--set") {
|
|
1634
|
+
if (command === "wip-cap") {
|
|
1635
|
+
// Python int() strips surrounding whitespace, so "--set ' 5'" parsed
|
|
1636
|
+
// cleanly; trim before the integer check to preserve that contract.
|
|
1637
|
+
const capText = value.trim();
|
|
1638
|
+
if (!/^[+-]?\d+$/.test(capText)) {
|
|
1639
|
+
return policySetError(`argument --set: invalid int value: '${value}'`);
|
|
1640
|
+
}
|
|
1641
|
+
args.cap = Number.parseInt(capText, 10);
|
|
1642
|
+
}
|
|
1643
|
+
else {
|
|
1644
|
+
// subagent-backend --set <choice>
|
|
1645
|
+
if (!KNOWN_SUBAGENT_BACKEND_IDS.has(value)) {
|
|
1646
|
+
const choices = [...KNOWN_SUBAGENT_BACKEND_IDS].sort().join(", ");
|
|
1647
|
+
return policySetError(`argument --set: invalid choice: '${value}' (choose from ${choices})`);
|
|
1648
|
+
}
|
|
1649
|
+
args.backendId = value;
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
if (command === "wip-cap" && args.cap === undefined) {
|
|
1654
|
+
return policySetError("the following arguments are required: --set");
|
|
1655
|
+
}
|
|
1656
|
+
if (command === "subagent-backend" && args.backendId === undefined) {
|
|
1657
|
+
return policySetError("the following arguments are required: --set");
|
|
1658
|
+
}
|
|
1659
|
+
return args;
|
|
1660
|
+
}
|
|
1661
|
+
/** Load PROJECT-DEFINITION for an in-place typed-field write (mirrors the .setdefault chain). */
|
|
1662
|
+
function loadProjectDefinitionForWrite(projectRoot) {
|
|
1663
|
+
const path = projectDefinitionPath(projectRoot);
|
|
1664
|
+
if (!existsSync(path)) {
|
|
1665
|
+
throw new PolicySetError(`PROJECT-DEFINITION not found at ${path}`, "not-found");
|
|
1666
|
+
}
|
|
1667
|
+
let parsed;
|
|
1668
|
+
try {
|
|
1669
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
1670
|
+
}
|
|
1671
|
+
catch (err) {
|
|
1672
|
+
throw new PolicySetError(`PROJECT-DEFINITION at ${path} is not valid JSON: ${String(err)}`, "config");
|
|
1673
|
+
}
|
|
1674
|
+
// JSON.parse can yield a non-object top level (null / array / scalar) without
|
|
1675
|
+
// throwing; reject it before the .plan/.policy property chain dereferences it.
|
|
1676
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1677
|
+
throw new PolicySetError(`PROJECT-DEFINITION at ${path} is not a JSON object`, "config");
|
|
1678
|
+
}
|
|
1679
|
+
const data = parsed;
|
|
1680
|
+
let plan = data.plan;
|
|
1681
|
+
if (plan === undefined) {
|
|
1682
|
+
plan = {};
|
|
1683
|
+
data.plan = plan;
|
|
1684
|
+
}
|
|
1685
|
+
if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
|
|
1686
|
+
throw new PolicySetError("PROJECT-DEFINITION 'plan' is not an object", "config");
|
|
1687
|
+
}
|
|
1688
|
+
const planObj = plan;
|
|
1689
|
+
let policy = planObj.policy;
|
|
1690
|
+
if (policy === undefined) {
|
|
1691
|
+
policy = {};
|
|
1692
|
+
planObj.policy = policy;
|
|
1693
|
+
}
|
|
1694
|
+
if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
|
|
1695
|
+
throw new PolicySetError("plan.policy is not an object", "config");
|
|
1696
|
+
}
|
|
1697
|
+
return { path, data, policyBlock: policy };
|
|
1698
|
+
}
|
|
1699
|
+
/** Write plan.policy.wipCap in place + append the audit row (mirrors set_wip_cap). */
|
|
1700
|
+
function writeWipCap(projectRoot, cap, actor, note) {
|
|
1701
|
+
const { path, data, policyBlock } = loadProjectDefinitionForWrite(projectRoot);
|
|
1702
|
+
const previous = policyBlock.wipCap;
|
|
1703
|
+
policyBlock.wipCap = cap;
|
|
1704
|
+
writeFileSync(path, `${JSON.stringify(data, null, 2)}\n`, "utf8");
|
|
1705
|
+
const changed = previous !== cap;
|
|
1706
|
+
const parts = [`actor=${actor}`, `wipCap=${cap}`, `previous=${pyRepr(previous)}`];
|
|
1707
|
+
if (note)
|
|
1708
|
+
parts.push(`note=${sanitizeNote(note)}`);
|
|
1709
|
+
const auditEntry = parts.join(" ");
|
|
1710
|
+
appendAuditLog(projectRoot, auditEntry);
|
|
1711
|
+
return { changed, auditEntry };
|
|
1712
|
+
}
|
|
1713
|
+
/** Write plan.policy.swarmSubagentBackend in place + append the audit row. */
|
|
1714
|
+
function writeSubagentBackend(projectRoot, backendId, actor, note) {
|
|
1715
|
+
const { path, data, policyBlock } = loadProjectDefinitionForWrite(projectRoot);
|
|
1716
|
+
const previous = policyBlock.swarmSubagentBackend;
|
|
1717
|
+
policyBlock.swarmSubagentBackend = backendId;
|
|
1718
|
+
writeFileSync(path, `${JSON.stringify(data, null, 2)}\n`, "utf8");
|
|
1719
|
+
const changed = previous !== backendId;
|
|
1720
|
+
const parts = [
|
|
1721
|
+
`actor=${actor}`,
|
|
1722
|
+
`swarmSubagentBackend=${backendId}`,
|
|
1723
|
+
`previous=${pyRepr(previous)}`,
|
|
1724
|
+
];
|
|
1725
|
+
if (note)
|
|
1726
|
+
parts.push(`note=${sanitizeNote(note)}`);
|
|
1727
|
+
const auditEntry = parts.join(" ");
|
|
1728
|
+
appendAuditLog(projectRoot, auditEntry);
|
|
1729
|
+
return { changed, auditEntry };
|
|
1730
|
+
}
|
|
1731
|
+
/** Serialise the probe output for `subagent-backends --format json`. */
|
|
1732
|
+
function subagentBackendsToJson(backends) {
|
|
1733
|
+
const payload = {
|
|
1734
|
+
backends: backends.map((entry) => ({
|
|
1735
|
+
id: entry.backend_id,
|
|
1736
|
+
display_name: entry.display_name,
|
|
1737
|
+
roles: [...entry.roles],
|
|
1738
|
+
available: entry.available,
|
|
1739
|
+
})),
|
|
343
1740
|
};
|
|
1741
|
+
return JSON.stringify(payload, null, 2);
|
|
1742
|
+
}
|
|
1743
|
+
/** Map a write-path error to its fail-closed message + exit code (mirrors the except blocks). */
|
|
1744
|
+
function reportPolicyWriteError(err, io) {
|
|
1745
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1746
|
+
if (err instanceof PolicySetError && err.kind === "not-found") {
|
|
1747
|
+
io.writeErr(`\u274c ${message}\n`);
|
|
1748
|
+
io.writeErr(" Recovery: run `task setup` to generate vbrief/PROJECT-DEFINITION.vbrief.json.\n");
|
|
1749
|
+
return 2;
|
|
1750
|
+
}
|
|
1751
|
+
io.writeErr(`\u274c Config error: ${message}\n`);
|
|
1752
|
+
return 2;
|
|
1753
|
+
}
|
|
1754
|
+
function applyBranchPolicy(args, io) {
|
|
1755
|
+
let target;
|
|
1756
|
+
if (args.cmd === "enforce-branches") {
|
|
1757
|
+
target = false;
|
|
1758
|
+
}
|
|
1759
|
+
else {
|
|
1760
|
+
if (!args.confirm) {
|
|
1761
|
+
io.writeOut(`${POLICY_CAPABILITY_COST_DISCLOSURE}\n`);
|
|
1762
|
+
io.writeOut("\n");
|
|
1763
|
+
io.writeOut("Re-run with --confirm to apply: task policy:allow-direct-commits -- --confirm\n");
|
|
1764
|
+
return 1;
|
|
1765
|
+
}
|
|
1766
|
+
target = true;
|
|
1767
|
+
}
|
|
1768
|
+
let result;
|
|
1769
|
+
try {
|
|
1770
|
+
result = setPolicy(args.projectRoot, {
|
|
1771
|
+
allowDirectCommits: target,
|
|
1772
|
+
actor: args.actor,
|
|
1773
|
+
note: args.note,
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
catch (err) {
|
|
1777
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1778
|
+
if (message.includes("PROJECT-DEFINITION not found")) {
|
|
1779
|
+
io.writeErr(`\u274c ${message}\n`);
|
|
1780
|
+
io.writeErr(" Recovery: run `task setup` to generate vbrief/PROJECT-DEFINITION.vbrief.json.\n");
|
|
1781
|
+
return 2;
|
|
1782
|
+
}
|
|
1783
|
+
io.writeErr(`\u274c Config error: ${message}\n`);
|
|
1784
|
+
return 2;
|
|
1785
|
+
}
|
|
1786
|
+
const state = target ? "OFF" : "ON";
|
|
1787
|
+
io.writeOut(`\u2713 plan.policy.allowDirectCommitsToMaster=${target ? "true" : "false"} ` +
|
|
1788
|
+
`(branch-protection ${state}).\n`);
|
|
1789
|
+
if (result.changed) {
|
|
1790
|
+
io.writeOut(` audit: meta/policy-changes.log :: ${result.auditEntry}\n`);
|
|
1791
|
+
}
|
|
1792
|
+
else {
|
|
1793
|
+
io.writeOut(" no-op: value already matched (audit entry still appended for trail).\n");
|
|
1794
|
+
}
|
|
1795
|
+
io.writeOut(`${disclosureLine(resolvePolicy(args.projectRoot))}\n`);
|
|
1796
|
+
return 0;
|
|
1797
|
+
}
|
|
1798
|
+
function applyWipCap(args, io) {
|
|
1799
|
+
const cap = args.cap ?? 0;
|
|
1800
|
+
if (cap < 0) {
|
|
1801
|
+
io.writeErr(`\u274c --set must be >= 0; got ${cap}.\n`);
|
|
1802
|
+
return 1;
|
|
1803
|
+
}
|
|
1804
|
+
if (!args.confirm) {
|
|
1805
|
+
io.writeOut(`${POLICY_WIP_CAP_DISCLOSURE}\n`);
|
|
1806
|
+
io.writeOut("\n");
|
|
1807
|
+
io.writeOut(`Re-run with --confirm to apply: task policy:wip-cap -- --set ${cap} --confirm\n`);
|
|
1808
|
+
return 1;
|
|
1809
|
+
}
|
|
1810
|
+
let res;
|
|
1811
|
+
try {
|
|
1812
|
+
res = writeWipCap(args.projectRoot, cap, args.actor, args.note);
|
|
1813
|
+
}
|
|
1814
|
+
catch (err) {
|
|
1815
|
+
return reportPolicyWriteError(err, io);
|
|
1816
|
+
}
|
|
1817
|
+
io.writeOut(`\u2713 plan.policy.wipCap=${cap}.\n`);
|
|
1818
|
+
if (res.changed) {
|
|
1819
|
+
io.writeOut(` audit: meta/policy-changes.log :: ${res.auditEntry}\n`);
|
|
1820
|
+
}
|
|
1821
|
+
else {
|
|
1822
|
+
io.writeOut(" no-op: value already matched (audit entry still appended for trail).\n");
|
|
1823
|
+
}
|
|
1824
|
+
const result = resolveWipCap(args.projectRoot);
|
|
1825
|
+
io.writeOut(`[deft policy] plan.policy.wipCap=${result.cap} (source: ${result.source}).\n`);
|
|
1826
|
+
return 0;
|
|
1827
|
+
}
|
|
1828
|
+
function applySubagentBackend(args, io) {
|
|
1829
|
+
const backendId = args.backendId ?? "";
|
|
1830
|
+
let res;
|
|
1831
|
+
try {
|
|
1832
|
+
res = writeSubagentBackend(args.projectRoot, backendId, args.actor, args.note);
|
|
1833
|
+
}
|
|
1834
|
+
catch (err) {
|
|
1835
|
+
return reportPolicyWriteError(err, io);
|
|
1836
|
+
}
|
|
1837
|
+
io.writeOut(`\u2713 plan.policy.swarmSubagentBackend=${backendId}.\n`);
|
|
1838
|
+
if (res.changed) {
|
|
1839
|
+
io.writeOut(` audit: meta/policy-changes.log :: ${res.auditEntry}\n`);
|
|
1840
|
+
}
|
|
1841
|
+
else {
|
|
1842
|
+
io.writeOut(" no-op: value already matched (audit entry still appended for trail).\n");
|
|
1843
|
+
}
|
|
1844
|
+
const result = resolveSwarmSubagentBackend(args.projectRoot);
|
|
1845
|
+
io.writeOut(`[deft policy] plan.policy.swarmSubagentBackend=${pyRepr(result.backend_id)} ` +
|
|
1846
|
+
`(source: ${result.source}).\n`);
|
|
1847
|
+
return 0;
|
|
1848
|
+
}
|
|
1849
|
+
function applySubagentBackends(args, io) {
|
|
1850
|
+
const entries = probeSubagentBackends();
|
|
1851
|
+
if (args.format === "json") {
|
|
1852
|
+
io.writeOut(`${subagentBackendsToJson(entries)}\n`);
|
|
1853
|
+
return 0;
|
|
1854
|
+
}
|
|
1855
|
+
for (const entry of entries) {
|
|
1856
|
+
const roles = entry.roles.join(", ");
|
|
1857
|
+
const avail = entry.available ? "available" : "unavailable";
|
|
1858
|
+
io.writeOut(`${entry.backend_id}\t${entry.display_name}\troles=[${roles}]\t${avail}\n`);
|
|
1859
|
+
}
|
|
1860
|
+
return 0;
|
|
1861
|
+
}
|
|
1862
|
+
/** Native `policy-set` dispatcher (replaces the policy_set.py shell-out, #2022 Phase 1). */
|
|
1863
|
+
export function runPolicySet(argv, io) {
|
|
1864
|
+
const args = parsePolicySetArgs(argv);
|
|
1865
|
+
if (args.error !== undefined) {
|
|
1866
|
+
io.writeErr(`policy-set: ${args.error}\n`);
|
|
1867
|
+
return 2;
|
|
1868
|
+
}
|
|
1869
|
+
switch (args.cmd) {
|
|
1870
|
+
case "enforce-branches":
|
|
1871
|
+
case "allow-direct-commits":
|
|
1872
|
+
return applyBranchPolicy(args, io);
|
|
1873
|
+
case "wip-cap":
|
|
1874
|
+
return applyWipCap(args, io);
|
|
1875
|
+
case "subagent-backend":
|
|
1876
|
+
return applySubagentBackend(args, io);
|
|
1877
|
+
case "subagent-backends":
|
|
1878
|
+
return applySubagentBackends(args, io);
|
|
1879
|
+
}
|
|
344
1880
|
}
|
|
345
1881
|
async function loadCoreModuleHandler(verb, io) {
|
|
346
1882
|
switch (verb) {
|
|
@@ -449,17 +1985,19 @@ async function loadCoreModuleHandler(verb, io) {
|
|
|
449
1985
|
};
|
|
450
1986
|
}
|
|
451
1987
|
case "pack-migrate-skills":
|
|
452
|
-
return
|
|
1988
|
+
return (argv) => runPackMigrateSkills(argv, io);
|
|
453
1989
|
case "pack-migrate-rules":
|
|
454
|
-
return
|
|
1990
|
+
return (argv) => runPackMigrateRules(argv, io);
|
|
455
1991
|
case "pack-migrate-strategies":
|
|
456
|
-
return
|
|
1992
|
+
return (argv) => runPackMigrateStrategies(argv, io);
|
|
457
1993
|
case "pack-migrate-patterns":
|
|
458
|
-
return
|
|
1994
|
+
return (argv) => runPackMigratePatterns(argv, io);
|
|
459
1995
|
case "pack-migrate-swarm-spec":
|
|
460
|
-
return
|
|
1996
|
+
return (argv) => runPackMigrateSwarmSpec(argv, io);
|
|
461
1997
|
case "policy-set":
|
|
462
|
-
return
|
|
1998
|
+
return (argv) => runPolicySet(argv, io);
|
|
1999
|
+
case "setup-ghx":
|
|
2000
|
+
return (argv) => runSetupGhx(argv, io);
|
|
463
2001
|
case "scope-undo": {
|
|
464
2002
|
const { undoMain } = await import("@deftai/directive-core/dist/scope/main.js");
|
|
465
2003
|
return undoMain;
|