cyber-sdd 0.7.0 → 0.7.2
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/.claude-plugin/plugin.json +1 -2
- package/.codex-plugin/plugin.json +1 -2
- package/CHANGELOG.md +320 -0
- package/agents/sdd-scanner.md +1 -1
- package/package.json +11 -12
- package/plugin.json +55 -0
- package/skills/align-spec/SKILL.md +1 -1
- package/skills/align-spec/scripts/align-spec.mjs +6755 -12
- package/skills/blast-estimate/README.md +3 -3
- package/skills/blast-estimate/scripts/blast-estimate.mjs +601 -12
- package/skills/check-field-mandates/scripts/check-field-mandates.mjs +253 -12
- package/skills/check-partition-quality/scripts/check-partition-quality.mjs +235 -12
- package/skills/check-plan-safety/SKILL.md +2 -3
- package/skills/check-plan-safety/scripts/check-plan-safety.mjs +109 -12
- package/skills/check-plugin-manifests/scripts/check-plugin-manifests.mjs +250 -12
- package/skills/check-project-specs/SKILL.md +1 -1
- package/skills/check-project-specs/scripts/check-project-specs.mjs +593 -12
- package/skills/check-retired-terms/SKILL.md +2 -3
- package/skills/check-retired-terms/scripts/check-retired-terms.mjs +186 -12
- package/skills/check-scenario-overlap/SKILL.md +1 -2
- package/skills/check-scenario-overlap/scripts/check-scenario-overlap.mjs +177 -12
- package/skills/check-spec-references/SKILL.md +2 -2
- package/skills/check-spec-references/scripts/check-spec-references.mjs +287 -12
- package/skills/check-spec-structure/SKILL.md +1 -2
- package/skills/check-spec-structure/scripts/check-spec-structure.mjs +243 -12
- package/skills/collision-ladder/README.md +2 -2
- package/skills/collision-ladder/scripts/collision-ladder.mjs +7047 -12
- package/skills/combat-log-governance/SKILL.md +1 -1
- package/skills/concept-index/SKILL.md +1 -1
- package/skills/concept-index/scripts/concept-index.mjs +183 -12
- package/skills/discover-plans/README.md +3 -3
- package/skills/discover-plans/SKILL.md +1 -1
- package/skills/discover-plans/scripts/discover-plans.mjs +137 -12
- package/skills/discover-specs/README.md +3 -3
- package/skills/discover-specs/SKILL.md +1 -1
- package/skills/discover-specs/scripts/discover-specs.mjs +321 -12
- package/skills/doctrine-loop/SKILL.md +1 -1
- package/skills/gate-validation-governance/SKILL.md +1 -1
- package/skills/init/README.md +2 -2
- package/skills/init/scripts/wire-statusline.mjs +168 -12
- package/skills/manage-ignore/README.md +3 -3
- package/skills/manage-ignore/SKILL.md +1 -2
- package/skills/manage-ignore/scripts/manage-ignore.mjs +285 -12
- package/skills/manage-scenario-bridge/README.md +3 -3
- package/skills/manage-scenario-bridge/SKILL.md +1 -1
- package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mjs +239 -12
- package/skills/manage-spec-anchors/README.md +3 -3
- package/skills/manage-spec-anchors/SKILL.md +1 -1
- package/skills/manage-spec-anchors/scripts/manage-spec-anchors.mjs +310 -12
- package/skills/mission-graph/README.md +2 -2
- package/skills/mission-graph/SKILL.md +1 -1
- package/skills/mission-graph/scripts/mission-graph.mjs +1040 -12
- package/skills/pause-mission/SKILL.md +1 -1
- package/skills/place-node/SKILL.md +2 -2
- package/skills/place-node/scripts/place-node.mjs +117 -12
- package/skills/plan-retirement/README.md +2 -2
- package/skills/plan-retirement/scripts/retire-plans.mjs +123 -12
- package/skills/resolve-governances/README.md +3 -3
- package/skills/resolve-governances/SKILL.md +1 -1
- package/skills/resolve-governances/scripts/resolve-governances.mjs +352 -12
- package/skills/resolve-tracking/SKILL.md +1 -1
- package/skills/resolve-tracking/scripts/resolve-tracking.mjs +152 -12
- package/skills/scaffold-project-spec/SKILL.md +1 -1
- package/skills/spec-gate/README.md +12 -9
- package/skills/spec-gate/SKILL.md +2 -2
- package/skills/spec-gate/scripts/check-spec-state.mjs +422 -12
- package/skills/spec-gate/scripts/check-suite.mjs +6500 -12
- package/skills/spec-gate/scripts/classify-edit-class.mjs +6579 -12
- package/skills/spec-producer-governance/SKILL.md +2 -2
- package/skills/start-mission/SKILL.md +1 -1
- package/skills/suite-format-governance/README.md +1 -1
- package/skills/suite-format-governance/SKILL.md +1 -1
- package/skills/touch-set-correction/README.md +5 -5
- package/skills/touch-set-correction/SKILL.md +1 -1
- package/skills/touch-set-correction/scripts/touch-set-correction.mjs +6663 -12
- package/skills/verify-scenarios/README.md +6 -6
- package/skills/verify-scenarios/scripts/verify-scenarios.mjs +6539 -12
- package/.plugin/pins.json +0 -3
- package/.plugin/plugin.json +0 -19
- package/dist/align-spec/scripts/align-spec.js +0 -102
- package/dist/check-project-specs/scripts/check-project-specs.js +0 -312
- package/dist/check-scenario-overlap/scripts/check-scenario-overlap.js +0 -175
- package/dist/check-spec-references/scripts/check-spec-references.js +0 -285
- package/dist/check-spec-structure/scripts/check-spec-structure.js +0 -241
- package/dist/concept-index/scripts/concept-index.js +0 -181
- package/dist/discover-specs/scripts/discover-specs.js +0 -318
- package/dist/spec-gate/scripts/check-spec-state.js +0 -420
- package/dist/spec-gate/scripts/check-suite.js +0 -317
- package/dist/spec-gate/scripts/classify-edit-class.js +0 -263
- package/dist/spec-gate/vendor/gherkin-cli.js +0 -6369
- package/skills/align-spec/scripts/align-spec.bundle.mjs +0 -6753
- package/skills/align-spec/scripts/align-spec.mts +0 -188
- package/skills/blast-estimate/scripts/blast-estimate.bundle.mjs +0 -593
- package/skills/blast-estimate/scripts/blast-estimate.mts +0 -586
- package/skills/check-field-mandates/scripts/check-field-mandates.bundle.mjs +0 -251
- package/skills/check-field-mandates/scripts/check-field-mandates.mts +0 -337
- package/skills/check-partition-quality/scripts/check-partition-quality.bundle.mjs +0 -233
- package/skills/check-partition-quality/scripts/check-partition-quality.mts +0 -338
- package/skills/check-plan-safety/scripts/check-plan-safety.bundle.mjs +0 -107
- package/skills/check-plan-safety/scripts/check-plan-safety.mts +0 -148
- package/skills/check-plugin-manifests/scripts/check-plugin-manifests.bundle.mjs +0 -248
- package/skills/check-plugin-manifests/scripts/check-plugin-manifests.mts +0 -303
- package/skills/check-project-specs/scripts/check-project-specs.bundle.mjs +0 -584
- package/skills/check-project-specs/scripts/check-project-specs.mts +0 -370
- package/skills/check-retired-terms/scripts/check-retired-terms.bundle.mjs +0 -184
- package/skills/check-retired-terms/scripts/check-retired-terms.mts +0 -293
- package/skills/check-scenario-overlap/scripts/check-scenario-overlap.bundle.mjs +0 -175
- package/skills/check-scenario-overlap/scripts/check-scenario-overlap.mts +0 -250
- package/skills/check-spec-references/scripts/check-spec-references.bundle.mjs +0 -285
- package/skills/check-spec-references/scripts/check-spec-references.mts +0 -422
- package/skills/check-spec-structure/scripts/check-spec-structure.bundle.mjs +0 -241
- package/skills/check-spec-structure/scripts/check-spec-structure.mts +0 -347
- package/skills/collision-ladder/scripts/collision-ladder.bundle.mjs +0 -7041
- package/skills/collision-ladder/scripts/collision-ladder.mts +0 -662
- package/skills/concept-index/scripts/concept-index.bundle.mjs +0 -181
- package/skills/concept-index/scripts/concept-index.mts +0 -246
- package/skills/discover-plans/scripts/discover-plans.bundle.mjs +0 -135
- package/skills/discover-plans/scripts/discover-plans.mts +0 -215
- package/skills/discover-specs/scripts/discover-specs.bundle.mjs +0 -319
- package/skills/discover-specs/scripts/discover-specs.mts +0 -420
- package/skills/init/scripts/wire-statusline.bundle.mjs +0 -166
- package/skills/init/scripts/wire-statusline.mts +0 -279
- package/skills/manage-ignore/scripts/manage-ignore.bundle.mjs +0 -244
- package/skills/manage-ignore/scripts/manage-ignore.mts +0 -297
- package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.bundle.mjs +0 -237
- package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mts +0 -159
- package/skills/manage-spec-anchors/scripts/manage-spec-anchors.bundle.mjs +0 -308
- package/skills/manage-spec-anchors/scripts/manage-spec-anchors.mts +0 -331
- package/skills/mission-graph/scripts/mission-graph.bundle.mjs +0 -1038
- package/skills/mission-graph/scripts/mission-graph.mts +0 -1333
- package/skills/place-node/scripts/place-node.bundle.mjs +0 -115
- package/skills/place-node/scripts/place-node.mts +0 -158
- package/skills/plan-retirement/scripts/retire-plans.bundle.mjs +0 -121
- package/skills/plan-retirement/scripts/retire-plans.mts +0 -205
- package/skills/resolve-governances/scripts/resolve-governances.bundle.mjs +0 -350
- package/skills/resolve-governances/scripts/resolve-governances.mts +0 -518
- package/skills/resolve-tracking/scripts/resolve-tracking.bundle.mjs +0 -150
- package/skills/resolve-tracking/scripts/resolve-tracking.mts +0 -214
- package/skills/spec-gate/scripts/check-spec-state.bundle.mjs +0 -420
- package/skills/spec-gate/scripts/check-spec-state.mts +0 -637
- package/skills/spec-gate/scripts/check-suite.bundle.mjs +0 -6498
- package/skills/spec-gate/scripts/check-suite.mts +0 -538
- package/skills/spec-gate/scripts/classify-edit-class.bundle.mjs +0 -6577
- package/skills/spec-gate/scripts/classify-edit-class.mts +0 -414
- package/skills/spec-gate/vendor/gherkin-cli.d.mts +0 -2
- package/skills/spec-gate/vendor/gherkin-cli.mjs +0 -6545
- package/skills/touch-set-correction/scripts/touch-set-correction.bundle.mjs +0 -6655
- package/skills/touch-set-correction/scripts/touch-set-correction.mts +0 -429
- package/skills/verify-scenarios/scripts/verify-scenarios.bundle.mjs +0 -6537
- package/skills/verify-scenarios/scripts/verify-scenarios.mts +0 -470
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { readFileSync, readdirSync, realpathSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { pathToFileURL } from "node:url";
|
|
5
|
-
//#region skills/place-node/scripts/place-node.mts
|
|
6
|
-
const SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
7
|
-
"node_modules",
|
|
8
|
-
".git",
|
|
9
|
-
"dist",
|
|
10
|
-
".turbo",
|
|
11
|
-
".next",
|
|
12
|
-
"coverage"
|
|
13
|
-
]);
|
|
14
|
-
function parseConcepts(text) {
|
|
15
|
-
const m = /^---\r?\n([\s\S]*?)\r?\n---\s*(?:\r?\n|$)/.exec(text);
|
|
16
|
-
if (!m) return [];
|
|
17
|
-
const lines = m[1].split("\n").map((l) => l.replace(/\r$/, ""));
|
|
18
|
-
for (let i = 0; i < lines.length; i++) {
|
|
19
|
-
const line = lines[i];
|
|
20
|
-
if (line.length - line.trimStart().length !== 0) continue;
|
|
21
|
-
const [key, ...rest] = line.trim().split(":");
|
|
22
|
-
if (key !== "concept") continue;
|
|
23
|
-
const value = rest.join(":").trim();
|
|
24
|
-
if (value.startsWith("[") && value.endsWith("]")) return value.slice(1, -1).split(",").map((s) => unquote(s.trim())).filter((s) => s.length > 0);
|
|
25
|
-
if (value !== "") return [unquote(value)];
|
|
26
|
-
const out = [];
|
|
27
|
-
for (let j = i + 1; j < lines.length; j++) {
|
|
28
|
-
const item = lines[j];
|
|
29
|
-
if (item.trim() === "") continue;
|
|
30
|
-
if (item.length - item.trimStart().length === 0) break;
|
|
31
|
-
const dash = /^\s*-\s+(.*)$/.exec(item);
|
|
32
|
-
if (dash) out.push(unquote(dash[1].trim()));
|
|
33
|
-
}
|
|
34
|
-
return out;
|
|
35
|
-
}
|
|
36
|
-
return [];
|
|
37
|
-
}
|
|
38
|
-
function unquote(v) {
|
|
39
|
-
return v.replace(/^["']|["']$/g, "");
|
|
40
|
-
}
|
|
41
|
-
function displayPath(relPath) {
|
|
42
|
-
const p = relPath.replace(/\\/g, "/");
|
|
43
|
-
return p.endsWith("/README.md") ? p.slice(0, -9) : p;
|
|
44
|
-
}
|
|
45
|
-
function scanProjectSpec(specDir) {
|
|
46
|
-
const records = [];
|
|
47
|
-
walk(specDir, specDir, records);
|
|
48
|
-
return records;
|
|
49
|
-
}
|
|
50
|
-
function walk(dir, specDir, out) {
|
|
51
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
52
|
-
if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
|
|
53
|
-
const full = join(dir, entry.name);
|
|
54
|
-
if (entry.isDirectory()) walk(full, specDir, out);
|
|
55
|
-
else if (entry.name.endsWith(".md")) {
|
|
56
|
-
const concepts = parseConcepts(readFileSync(full, "utf8"));
|
|
57
|
-
if (concepts.length === 0) continue;
|
|
58
|
-
const relPath = full.slice(specDir.length + 1).replace(/\\/g, "/");
|
|
59
|
-
out.push({
|
|
60
|
-
relPath,
|
|
61
|
-
capability: relPath.split("/")[0],
|
|
62
|
-
display: displayPath(relPath),
|
|
63
|
-
concepts
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
function suggestHomes(records, concept) {
|
|
69
|
-
const counts = /* @__PURE__ */ new Map();
|
|
70
|
-
for (const rec of records) {
|
|
71
|
-
if (!rec.concepts.includes(concept)) continue;
|
|
72
|
-
counts.set(rec.capability, (counts.get(rec.capability) ?? 0) + 1);
|
|
73
|
-
}
|
|
74
|
-
return [...counts.entries()].map(([capability, count]) => ({
|
|
75
|
-
capability,
|
|
76
|
-
count
|
|
77
|
-
})).sort((a, b) => b.count - a.count || a.capability.localeCompare(b.capability));
|
|
78
|
-
}
|
|
79
|
-
function findNear(records, name) {
|
|
80
|
-
const needle = name.trim().toLowerCase();
|
|
81
|
-
if (needle === "") return [];
|
|
82
|
-
return records.filter((rec) => rec.relPath.toLowerCase().includes(needle)).sort((a, b) => a.display.localeCompare(b.display));
|
|
83
|
-
}
|
|
84
|
-
function render(homes, near, concept, name) {
|
|
85
|
-
const lines = [`place-node: concept=${concept || "(none)"} name=${name || "(none)"}`];
|
|
86
|
-
if (homes.length > 0) {
|
|
87
|
-
lines.push(`homes[${homes.length}]{capability,facets}:`);
|
|
88
|
-
for (const h of homes) lines.push(` ${h.capability},${h.count}`);
|
|
89
|
-
} else lines.push("homes[0]: (new concept — pick any plausible capability; finalized at handoff)");
|
|
90
|
-
if (near.length > 0) {
|
|
91
|
-
lines.push(`near[${near.length}]:`);
|
|
92
|
-
for (const n of near) lines.push(` ${n.display}`);
|
|
93
|
-
} else lines.push("near[0]: (no name overlap)");
|
|
94
|
-
lines.push("note: placement is provisional — finalized at handoff");
|
|
95
|
-
return lines.join("\n");
|
|
96
|
-
}
|
|
97
|
-
function main(argv) {
|
|
98
|
-
let specDir = ".";
|
|
99
|
-
let concept = "";
|
|
100
|
-
let name = "";
|
|
101
|
-
for (let i = 0; i < argv.length; i++) {
|
|
102
|
-
const a = argv[i];
|
|
103
|
-
if (a === "--spec-dir") specDir = argv[++i] ?? ".";
|
|
104
|
-
else if (a === "--concept") concept = argv[++i] ?? "";
|
|
105
|
-
else if (a === "--name") name = argv[++i] ?? "";
|
|
106
|
-
}
|
|
107
|
-
const records = scanProjectSpec(specDir);
|
|
108
|
-
const homes = concept ? suggestHomes(records, concept) : [];
|
|
109
|
-
const near = name ? findNear(records, name) : [];
|
|
110
|
-
process.stdout.write(`${render(homes, near, concept, name)}\n`);
|
|
111
|
-
return 0;
|
|
112
|
-
}
|
|
113
|
-
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) process.exit(main(process.argv.slice(2)));
|
|
114
|
-
//#endregion
|
|
115
|
-
export { findNear, main, parseConcepts, scanProjectSpec, suggestHomes };
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// place-node — project-spec/place-node's concrete engine. Given a new node's concept (and optional name),
|
|
3
|
-
// suggests a provisional capability home and surfaces possible duplicates, so explore places a node in
|
|
4
|
-
// one lookup instead of holding the tree in its head.
|
|
5
|
-
//
|
|
6
|
-
// Derivation, not a registry: the home for a concept is where that concept's facets already sit — read
|
|
7
|
-
// live from the project-spec's `concept:` tags, never a stored routing list (the corpus/discovery no-drift
|
|
8
|
-
// rule). Read-only and advisory: it writes nothing, and placement is finalized at handoff.
|
|
9
|
-
//
|
|
10
|
-
// No dependencies (the repo's node-≥23.6 / no-deps convention). Pure functions are exported for
|
|
11
|
-
// node:test; running the file directly drives the CLI.
|
|
12
|
-
|
|
13
|
-
import { readdirSync, readFileSync, realpathSync } from 'node:fs'
|
|
14
|
-
import { join } from 'node:path'
|
|
15
|
-
import { pathToFileURL } from 'node:url'
|
|
16
|
-
|
|
17
|
-
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', '.turbo', '.next', 'coverage'])
|
|
18
|
-
|
|
19
|
-
export interface NodeRecord {
|
|
20
|
-
/** Path relative to the spec directory (POSIX). */
|
|
21
|
-
relPath: string
|
|
22
|
-
/** The top-level folder — the capability. */
|
|
23
|
-
capability: string
|
|
24
|
-
/** Display form — a README.md node shows its folder, a design doc shows the file. */
|
|
25
|
-
display: string
|
|
26
|
-
concepts: string[]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface HomeSuggestion {
|
|
30
|
-
capability: string
|
|
31
|
-
count: number
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// ── Frontmatter parse (concept only — the placement signal) ──
|
|
35
|
-
export function parseConcepts(text: string): string[] {
|
|
36
|
-
const m = /^---\r?\n([\s\S]*?)\r?\n---\s*(?:\r?\n|$)/.exec(text)
|
|
37
|
-
if (!m) return []
|
|
38
|
-
const lines = m[1].split('\n').map((l) => l.replace(/\r$/, ''))
|
|
39
|
-
for (let i = 0; i < lines.length; i++) {
|
|
40
|
-
const line = lines[i]
|
|
41
|
-
if (line.length - line.trimStart().length !== 0) continue
|
|
42
|
-
const [key, ...rest] = line.trim().split(':')
|
|
43
|
-
if (key !== 'concept') continue
|
|
44
|
-
const value = rest.join(':').trim()
|
|
45
|
-
if (value.startsWith('[') && value.endsWith(']')) {
|
|
46
|
-
return value
|
|
47
|
-
.slice(1, -1)
|
|
48
|
-
.split(',')
|
|
49
|
-
.map((s) => unquote(s.trim()))
|
|
50
|
-
.filter((s) => s.length > 0)
|
|
51
|
-
}
|
|
52
|
-
if (value !== '') return [unquote(value)]
|
|
53
|
-
// block list
|
|
54
|
-
const out: string[] = []
|
|
55
|
-
for (let j = i + 1; j < lines.length; j++) {
|
|
56
|
-
const item = lines[j]
|
|
57
|
-
if (item.trim() === '') continue
|
|
58
|
-
if (item.length - item.trimStart().length === 0) break
|
|
59
|
-
const dash = /^\s*-\s+(.*)$/.exec(item)
|
|
60
|
-
if (dash) out.push(unquote(dash[1].trim()))
|
|
61
|
-
}
|
|
62
|
-
return out
|
|
63
|
-
}
|
|
64
|
-
return []
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function unquote(v: string): string {
|
|
68
|
-
return v.replace(/^["']|["']$/g, '')
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function displayPath(relPath: string): string {
|
|
72
|
-
const p = relPath.replace(/\\/g, '/')
|
|
73
|
-
return p.endsWith('/README.md') ? p.slice(0, -'README.md'.length) : p
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// ── Scan the project-spec — every concept-tagged node ──
|
|
77
|
-
export function scanProjectSpec(specDir: string): NodeRecord[] {
|
|
78
|
-
const records: NodeRecord[] = []
|
|
79
|
-
walk(specDir, specDir, records)
|
|
80
|
-
return records
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function walk(dir: string, specDir: string, out: NodeRecord[]): void {
|
|
84
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
85
|
-
if (entry.name.startsWith('.') || SKIP_DIRS.has(entry.name)) continue
|
|
86
|
-
const full = join(dir, entry.name)
|
|
87
|
-
if (entry.isDirectory()) {
|
|
88
|
-
walk(full, specDir, out)
|
|
89
|
-
} else if (entry.name.endsWith('.md')) {
|
|
90
|
-
const concepts = parseConcepts(readFileSync(full, 'utf8'))
|
|
91
|
-
if (concepts.length === 0) continue
|
|
92
|
-
const relPath = full.slice(specDir.length + 1).replace(/\\/g, '/')
|
|
93
|
-
out.push({ relPath, capability: relPath.split('/')[0], display: displayPath(relPath), concepts })
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// ── Suggest a home — where the concept's facets already sit, ranked by count ──
|
|
99
|
-
export function suggestHomes(records: NodeRecord[], concept: string): HomeSuggestion[] {
|
|
100
|
-
const counts = new Map<string, number>()
|
|
101
|
-
for (const rec of records) {
|
|
102
|
-
if (!rec.concepts.includes(concept)) continue
|
|
103
|
-
counts.set(rec.capability, (counts.get(rec.capability) ?? 0) + 1)
|
|
104
|
-
}
|
|
105
|
-
return [...counts.entries()]
|
|
106
|
-
.map(([capability, count]) => ({ capability, count }))
|
|
107
|
-
.sort((a, b) => b.count - a.count || a.capability.localeCompare(b.capability))
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// ── Duplicate-catch — existing nodes whose path/name overlaps the candidate name ──
|
|
111
|
-
export function findNear(records: NodeRecord[], name: string): NodeRecord[] {
|
|
112
|
-
const needle = name.trim().toLowerCase()
|
|
113
|
-
if (needle === '') return []
|
|
114
|
-
return records
|
|
115
|
-
.filter((rec) => rec.relPath.toLowerCase().includes(needle))
|
|
116
|
-
.sort((a, b) => a.display.localeCompare(b.display))
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// ── Render ──
|
|
120
|
-
function render(homes: HomeSuggestion[], near: NodeRecord[], concept: string, name: string): string {
|
|
121
|
-
const lines: string[] = [`place-node: concept=${concept || '(none)'} name=${name || '(none)'}`]
|
|
122
|
-
if (homes.length > 0) {
|
|
123
|
-
lines.push(`homes[${homes.length}]{capability,facets}:`)
|
|
124
|
-
for (const h of homes) lines.push(` ${h.capability},${h.count}`)
|
|
125
|
-
} else {
|
|
126
|
-
lines.push('homes[0]: (new concept — pick any plausible capability; finalized at handoff)')
|
|
127
|
-
}
|
|
128
|
-
if (near.length > 0) {
|
|
129
|
-
lines.push(`near[${near.length}]:`)
|
|
130
|
-
for (const n of near) lines.push(` ${n.display}`)
|
|
131
|
-
} else {
|
|
132
|
-
lines.push('near[0]: (no name overlap)')
|
|
133
|
-
}
|
|
134
|
-
lines.push('note: placement is provisional — finalized at handoff')
|
|
135
|
-
return lines.join('\n')
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ── CLI ──
|
|
139
|
-
export function main(argv: string[]): number {
|
|
140
|
-
let specDir = '.'
|
|
141
|
-
let concept = ''
|
|
142
|
-
let name = ''
|
|
143
|
-
for (let i = 0; i < argv.length; i++) {
|
|
144
|
-
const a = argv[i]
|
|
145
|
-
if (a === '--spec-dir') specDir = argv[++i] ?? '.'
|
|
146
|
-
else if (a === '--concept') concept = argv[++i] ?? ''
|
|
147
|
-
else if (a === '--name') name = argv[++i] ?? ''
|
|
148
|
-
}
|
|
149
|
-
const records = scanProjectSpec(specDir)
|
|
150
|
-
const homes = concept ? suggestHomes(records, concept) : []
|
|
151
|
-
const near = name ? findNear(records, name) : []
|
|
152
|
-
process.stdout.write(`${render(homes, near, concept, name)}\n`)
|
|
153
|
-
return 0
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
157
|
-
process.exit(main(process.argv.slice(2)))
|
|
158
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, readFileSync, readdirSync, realpathSync, unlinkSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { pathToFileURL } from "node:url";
|
|
5
|
-
//#region skills/plan-retirement/scripts/retire-plans.mts
|
|
6
|
-
const PLAN_SUFFIX = ".plan.md";
|
|
7
|
-
const LOG_SUFFIX = ".log.jsonl";
|
|
8
|
-
const DESIGN_SUFFIX = ".design.md";
|
|
9
|
-
const OPERATIONS_SUFFIX = ".operations.md";
|
|
10
|
-
const EVIDENCE_SUFFIX = ".evidence.md";
|
|
11
|
-
function transientArtifactFiles(crRef) {
|
|
12
|
-
return [
|
|
13
|
-
`${crRef}${PLAN_SUFFIX}`,
|
|
14
|
-
`${crRef}${LOG_SUFFIX}`,
|
|
15
|
-
`${crRef}${DESIGN_SUFFIX}`,
|
|
16
|
-
`${crRef}${OPERATIONS_SUFFIX}`,
|
|
17
|
-
`${crRef}${EVIDENCE_SUFFIX}`
|
|
18
|
-
];
|
|
19
|
-
}
|
|
20
|
-
function parseCleared(value) {
|
|
21
|
-
if (!value) return [];
|
|
22
|
-
const seen = /* @__PURE__ */ new Set();
|
|
23
|
-
for (const raw of value.split(",")) {
|
|
24
|
-
const ref = raw.trim();
|
|
25
|
-
if (ref) seen.add(ref);
|
|
26
|
-
}
|
|
27
|
-
return [...seen];
|
|
28
|
-
}
|
|
29
|
-
function discoverPlans(root) {
|
|
30
|
-
let entries;
|
|
31
|
-
try {
|
|
32
|
-
entries = readdirSync(root);
|
|
33
|
-
} catch {
|
|
34
|
-
return [];
|
|
35
|
-
}
|
|
36
|
-
return entries.filter((f) => f.endsWith(PLAN_SUFFIX)).map((f) => f.slice(0, -8));
|
|
37
|
-
}
|
|
38
|
-
function discoverLogs(root) {
|
|
39
|
-
let entries;
|
|
40
|
-
try {
|
|
41
|
-
entries = readdirSync(root);
|
|
42
|
-
} catch {
|
|
43
|
-
return [];
|
|
44
|
-
}
|
|
45
|
-
return entries.filter((f) => f.endsWith(LOG_SUFFIX)).map((f) => f.slice(0, -10));
|
|
46
|
-
}
|
|
47
|
-
function distilledCrRefs(ledgerDir) {
|
|
48
|
-
const distilled = /* @__PURE__ */ new Set();
|
|
49
|
-
let entries;
|
|
50
|
-
try {
|
|
51
|
-
entries = readdirSync(ledgerDir);
|
|
52
|
-
} catch {
|
|
53
|
-
return distilled;
|
|
54
|
-
}
|
|
55
|
-
for (const entry of entries) {
|
|
56
|
-
if (!entry.endsWith(".jsonl")) continue;
|
|
57
|
-
let text;
|
|
58
|
-
try {
|
|
59
|
-
text = readFileSync(join(ledgerDir, entry), "utf8");
|
|
60
|
-
} catch {
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
for (const line of text.split("\n")) {
|
|
64
|
-
const trimmed = line.trim();
|
|
65
|
-
if (!trimmed) continue;
|
|
66
|
-
let record;
|
|
67
|
-
try {
|
|
68
|
-
record = JSON.parse(trimmed);
|
|
69
|
-
} catch {
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
if (record === null || typeof record !== "object") continue;
|
|
73
|
-
const { kind, distills } = record;
|
|
74
|
-
if (kind === "strategy" && typeof distills === "string" && distills.length > 0) distilled.add(distills);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return distilled;
|
|
78
|
-
}
|
|
79
|
-
function decideRetirements(cleared, existing, distilled, logPresent) {
|
|
80
|
-
const present = new Set(existing);
|
|
81
|
-
const seen = /* @__PURE__ */ new Set();
|
|
82
|
-
const out = [];
|
|
83
|
-
for (const ref of cleared) if (present.has(ref) && (distilled.has(ref) || !logPresent.has(ref)) && !seen.has(ref)) {
|
|
84
|
-
seen.add(ref);
|
|
85
|
-
out.push(ref);
|
|
86
|
-
}
|
|
87
|
-
return out;
|
|
88
|
-
}
|
|
89
|
-
function main(argv) {
|
|
90
|
-
const root = argv.includes("--root") ? argv[argv.indexOf("--root") + 1] : ".agents/plans";
|
|
91
|
-
const ledgerArg = argv.includes("--ledger") ? argv[argv.indexOf("--ledger") + 1] : void 0;
|
|
92
|
-
const cleared = parseCleared(argv.includes("--retire") ? argv[argv.indexOf("--retire") + 1] : void 0);
|
|
93
|
-
const dryRun = argv.includes("--dry-run");
|
|
94
|
-
if (argv.includes("--distilled")) {
|
|
95
|
-
for (const ref of distilledCrRefs(argv[argv.indexOf("--distilled") + 1] ?? "")) process.stdout.write(`${ref}\n`);
|
|
96
|
-
return 0;
|
|
97
|
-
}
|
|
98
|
-
let retiring;
|
|
99
|
-
if (!ledgerArg || !existsSync(ledgerArg)) {
|
|
100
|
-
process.stdout.write(`no verifiable ledger (${ledgerArg ? `--ledger ${ledgerArg} unreadable` : "--ledger not given"}): distillation cannot be checked, so retirement is skipped for all cr-refs\n`);
|
|
101
|
-
retiring = [];
|
|
102
|
-
} else {
|
|
103
|
-
const distilled = distilledCrRefs(ledgerArg);
|
|
104
|
-
const logPresent = new Set(discoverLogs(root));
|
|
105
|
-
retiring = decideRetirements(cleared, discoverPlans(root), distilled, logPresent);
|
|
106
|
-
}
|
|
107
|
-
const deleted = [];
|
|
108
|
-
for (const ref of retiring) for (const file of transientArtifactFiles(ref)) {
|
|
109
|
-
const path = join(root, file);
|
|
110
|
-
if (!existsSync(path)) continue;
|
|
111
|
-
if (!dryRun) unlinkSync(path);
|
|
112
|
-
deleted.push(path);
|
|
113
|
-
}
|
|
114
|
-
const verb = dryRun ? "would delete" : "deleted";
|
|
115
|
-
for (const path of deleted) process.stdout.write(`${verb} ${path}\n`);
|
|
116
|
-
process.stdout.write(`${dryRun ? "dry-run: " : ""}retired ${retiring.length} plan(s), ${deleted.length} file(s)\n`);
|
|
117
|
-
return 0;
|
|
118
|
-
}
|
|
119
|
-
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) process.exit(main(process.argv.slice(2)));
|
|
120
|
-
//#endregion
|
|
121
|
-
export { decideRetirements, discoverLogs, discoverPlans, distilledCrRefs, main, parseCleared, transientArtifactFiles };
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Plan retirement — the Doctrine loop's last retro step (see sdd:combat-log-governance,
|
|
3
|
-
// "Plan retirement"). A retired plan leaves the tree by a deliberate, gated TRACKED
|
|
4
|
-
// DELETION, never a gitignore side effect: for each cleared <cr-ref>, the sweep deletes that
|
|
5
|
-
// CR's whole TRANSIENT ARTIFACT SET — the plan pair (<cr-ref>.plan.md + <cr-ref>.log.jsonl)
|
|
6
|
-
// plus its transient CR-level planning briefs (<cr-ref>.design.md, <cr-ref>.operations.md,
|
|
7
|
-
// <cr-ref>.evidence.md) — from `.agents/plans`.
|
|
8
|
-
//
|
|
9
|
-
// The two gating signals split by verifiability. Source = done/merged stays the CALLER's
|
|
10
|
-
// judgment: the source-status query (github-NN -> GH issue, asana-<gid> -> Asana,
|
|
11
|
-
// local-<slug> -> the local store) needs network/gh, so the Scanner (doctrine-loop delegate)
|
|
12
|
-
// determines it and passes the CLEARED set via --retire. Distilled is local and mechanically
|
|
13
|
-
// checkable, so the sweep VERIFIES IT ITSELF against the project ledger (--ledger <dir>): a
|
|
14
|
-
// `strategy` entry whose `distills` field equals the <cr-ref> must exist. The distilled gate
|
|
15
|
-
// only guards a cr-ref whose combat log EXISTS — there is something on disk it could still
|
|
16
|
-
// distill from, so retirement without a distilling entry would be data loss. A cr-ref whose
|
|
17
|
-
// <cr-ref>.log.jsonl was never written has nothing to distill from in the first place, so it
|
|
18
|
-
// retires on clearance + presence alone, no distilling entry required. This is the
|
|
19
|
-
// mechanical, fail-closed gate + filesystem act:
|
|
20
|
-
// - it deletes the transient artifact set for a cr-ref that is cleared AND present on disk
|
|
21
|
-
// AND (distilled per the ledger OR has no log.jsonl to distill from);
|
|
22
|
-
// - a missing/unreadable --ledger skips retirement for ALL cr-refs (the no-log branch only
|
|
23
|
-
// applies once a ledger is actually present to consult);
|
|
24
|
-
// - anything not cleared, not present, or already gone is a no-op (idempotent, safe to
|
|
25
|
-
// re-run);
|
|
26
|
-
// - it never touches a plan it was not explicitly cleared to retire (fail-closed).
|
|
27
|
-
//
|
|
28
|
-
// Two invariants on the transient briefs (design.md / operations.md / evidence.md), both
|
|
29
|
-
// deliberate:
|
|
30
|
-
// - THEY DO NOT WIDEN THE DISTILLED GATE. The gate keys on the combat log's presence only
|
|
31
|
-
// (discoverLogs / logPresent) — a cr-ref with briefs but no log.jsonl still retires
|
|
32
|
-
// without a distilling strategy. A brief owes no distillation (its content was consumed
|
|
33
|
-
// by the mission itself, not extracted into the ledger), so gating on one would re-strand
|
|
34
|
-
// the no-log mission class the no-log branch exists to rescue.
|
|
35
|
-
// - THEY DO NOT ANCHOR PRESENCE. discoverPlans (keyed on .plan.md) stays the sole presence
|
|
36
|
-
// signal. A cr-ref with a design.md but no plan.md is a no-op — the idempotency contract
|
|
37
|
-
// (a cleared cr-ref with no plan on disk deletes nothing) is the stronger guarantee.
|
|
38
|
-
//
|
|
39
|
-
// Pure functions are exported for node:test; running the file directly drives the CLI.
|
|
40
|
-
// No dependencies. Use --dry-run to print the planned deletions without touching the tree.
|
|
41
|
-
|
|
42
|
-
import { existsSync, readdirSync, readFileSync, realpathSync, unlinkSync } from 'node:fs'
|
|
43
|
-
import { join } from 'node:path'
|
|
44
|
-
import { pathToFileURL } from 'node:url'
|
|
45
|
-
|
|
46
|
-
const PLAN_SUFFIX = '.plan.md'
|
|
47
|
-
const LOG_SUFFIX = '.log.jsonl'
|
|
48
|
-
const DESIGN_SUFFIX = '.design.md'
|
|
49
|
-
const OPERATIONS_SUFFIX = '.operations.md'
|
|
50
|
-
const EVIDENCE_SUFFIX = '.evidence.md'
|
|
51
|
-
|
|
52
|
-
// The full transient artifact set a retired cr-ref owns, in deletion order: the plan pair
|
|
53
|
-
// (plan.md, log.jsonl) then the optional transient CR-level planning briefs (design.md,
|
|
54
|
-
// operations.md, evidence.md). The briefs are optional — the per-file existsSync guard in
|
|
55
|
-
// main() no-ops any that are absent, which is what lets a cr-ref with only some briefs retire
|
|
56
|
-
// cleanly.
|
|
57
|
-
export function transientArtifactFiles(crRef: string): string[] {
|
|
58
|
-
return [
|
|
59
|
-
`${crRef}${PLAN_SUFFIX}`,
|
|
60
|
-
`${crRef}${LOG_SUFFIX}`,
|
|
61
|
-
`${crRef}${DESIGN_SUFFIX}`,
|
|
62
|
-
`${crRef}${OPERATIONS_SUFFIX}`,
|
|
63
|
-
`${crRef}${EVIDENCE_SUFFIX}`,
|
|
64
|
-
]
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Parse a --retire value (comma-separated cr-refs) into a clean, de-duplicated list.
|
|
68
|
-
export function parseCleared(value: string | undefined): string[] {
|
|
69
|
-
if (!value) return []
|
|
70
|
-
const seen = new Set<string>()
|
|
71
|
-
for (const raw of value.split(',')) {
|
|
72
|
-
const ref = raw.trim()
|
|
73
|
-
if (ref) seen.add(ref)
|
|
74
|
-
}
|
|
75
|
-
return [...seen]
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// The cr-refs that have a <cr-ref>.plan.md on disk under `root`.
|
|
79
|
-
export function discoverPlans(root: string): string[] {
|
|
80
|
-
let entries: string[]
|
|
81
|
-
try {
|
|
82
|
-
entries = readdirSync(root)
|
|
83
|
-
} catch {
|
|
84
|
-
return []
|
|
85
|
-
}
|
|
86
|
-
return entries.filter((f) => f.endsWith(PLAN_SUFFIX)).map((f) => f.slice(0, -PLAN_SUFFIX.length))
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// The cr-refs that have a <cr-ref>.log.jsonl on disk under `root`.
|
|
90
|
-
export function discoverLogs(root: string): string[] {
|
|
91
|
-
let entries: string[]
|
|
92
|
-
try {
|
|
93
|
-
entries = readdirSync(root)
|
|
94
|
-
} catch {
|
|
95
|
-
return []
|
|
96
|
-
}
|
|
97
|
-
return entries.filter((f) => f.endsWith(LOG_SUFFIX)).map((f) => f.slice(0, -LOG_SUFFIX.length))
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// The cr-refs the project ledger records as DISTILLED — a `strategy` entry whose `distills`
|
|
101
|
-
// field equals the cr-ref exists in some *.jsonl shard under `ledgerDir`. Keys on the
|
|
102
|
-
// structured `distills` field only: a cr-ref named merely inside a strategy's `evidence`
|
|
103
|
-
// array (a cross-reference) does NOT count, and an unratified entry (`ratified: false`, the
|
|
104
|
-
// Scanner's default) still counts — the gate is about what was distilled, not sign-off.
|
|
105
|
-
// Malformed lines and a missing/unreadable ledger dir are tolerated: fail-closed means an
|
|
106
|
-
// unverifiable ledger yields an EMPTY set, never a thrown error.
|
|
107
|
-
export function distilledCrRefs(ledgerDir: string): Set<string> {
|
|
108
|
-
const distilled = new Set<string>()
|
|
109
|
-
let entries: string[]
|
|
110
|
-
try {
|
|
111
|
-
entries = readdirSync(ledgerDir)
|
|
112
|
-
} catch {
|
|
113
|
-
return distilled
|
|
114
|
-
}
|
|
115
|
-
for (const entry of entries) {
|
|
116
|
-
if (!entry.endsWith('.jsonl')) continue
|
|
117
|
-
let text: string
|
|
118
|
-
try {
|
|
119
|
-
text = readFileSync(join(ledgerDir, entry), 'utf8')
|
|
120
|
-
} catch {
|
|
121
|
-
continue
|
|
122
|
-
}
|
|
123
|
-
for (const line of text.split('\n')) {
|
|
124
|
-
const trimmed = line.trim()
|
|
125
|
-
if (!trimmed) continue
|
|
126
|
-
let record: unknown
|
|
127
|
-
try {
|
|
128
|
-
record = JSON.parse(trimmed)
|
|
129
|
-
} catch {
|
|
130
|
-
continue
|
|
131
|
-
}
|
|
132
|
-
if (record === null || typeof record !== 'object') continue
|
|
133
|
-
const { kind, distills } = record as { kind?: unknown; distills?: unknown }
|
|
134
|
-
if (kind === 'strategy' && typeof distills === 'string' && distills.length > 0) distilled.add(distills)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return distilled
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Fail-closed, idempotent decision: retire exactly the cr-refs that are cleared by the caller,
|
|
141
|
-
// present on disk, AND either distilled per the ledger OR have no combat log to distill from
|
|
142
|
-
// (log.jsonl absent). An uncleared or absent plan is never retired; a present plan whose log
|
|
143
|
-
// DOES exist still requires a distilling entry (data-loss guard, unchanged). Order follows the
|
|
144
|
-
// cleared list for stable reporting.
|
|
145
|
-
export function decideRetirements(
|
|
146
|
-
cleared: string[],
|
|
147
|
-
existing: string[],
|
|
148
|
-
distilled: Set<string>,
|
|
149
|
-
logPresent: Set<string>,
|
|
150
|
-
): string[] {
|
|
151
|
-
const present = new Set(existing)
|
|
152
|
-
const seen = new Set<string>()
|
|
153
|
-
const out: string[] = []
|
|
154
|
-
for (const ref of cleared) {
|
|
155
|
-
if (present.has(ref) && (distilled.has(ref) || !logPresent.has(ref)) && !seen.has(ref)) {
|
|
156
|
-
seen.add(ref)
|
|
157
|
-
out.push(ref)
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return out
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export function main(argv: string[]): number {
|
|
164
|
-
const root = argv.includes('--root') ? argv[argv.indexOf('--root') + 1] : '.agents/plans'
|
|
165
|
-
const ledgerArg = argv.includes('--ledger') ? argv[argv.indexOf('--ledger') + 1] : undefined
|
|
166
|
-
const cleared = parseCleared(argv.includes('--retire') ? argv[argv.indexOf('--retire') + 1] : undefined)
|
|
167
|
-
const dryRun = argv.includes('--dry-run')
|
|
168
|
-
|
|
169
|
-
// `--distilled <ledger-dir>`: list the cr-refs the ledger records as distilled, one per line.
|
|
170
|
-
if (argv.includes('--distilled')) {
|
|
171
|
-
for (const ref of distilledCrRefs(argv[argv.indexOf('--distilled') + 1] ?? '')) process.stdout.write(`${ref}\n`)
|
|
172
|
-
return 0
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
let retiring: string[]
|
|
176
|
-
if (!ledgerArg || !existsSync(ledgerArg)) {
|
|
177
|
-
process.stdout.write(
|
|
178
|
-
`no verifiable ledger (${ledgerArg ? `--ledger ${ledgerArg} unreadable` : '--ledger not given'}): distillation cannot be checked, so retirement is skipped for all cr-refs\n`,
|
|
179
|
-
)
|
|
180
|
-
retiring = []
|
|
181
|
-
} else {
|
|
182
|
-
const distilled = distilledCrRefs(ledgerArg)
|
|
183
|
-
const logPresent = new Set(discoverLogs(root))
|
|
184
|
-
retiring = decideRetirements(cleared, discoverPlans(root), distilled, logPresent)
|
|
185
|
-
}
|
|
186
|
-
const deleted: string[] = []
|
|
187
|
-
|
|
188
|
-
for (const ref of retiring) {
|
|
189
|
-
for (const file of transientArtifactFiles(ref)) {
|
|
190
|
-
const path = join(root, file)
|
|
191
|
-
if (!existsSync(path)) continue // the log may be absent; delete what is there
|
|
192
|
-
if (!dryRun) unlinkSync(path)
|
|
193
|
-
deleted.push(path)
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const verb = dryRun ? 'would delete' : 'deleted'
|
|
198
|
-
for (const path of deleted) process.stdout.write(`${verb} ${path}\n`)
|
|
199
|
-
process.stdout.write(`${dryRun ? 'dry-run: ' : ''}retired ${retiring.length} plan(s), ${deleted.length} file(s)\n`)
|
|
200
|
-
return 0
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
204
|
-
process.exit(main(process.argv.slice(2)))
|
|
205
|
-
}
|