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,420 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// discover-specs — corpus/discovery's concrete frontmatter engine. Scans the three
|
|
3
|
-
// SDD spec locations, filters candidates by the lifecycle `status` shape, parses each
|
|
4
|
-
// spec.md's frontmatter ONLY (never its body), and emits a TOON list of the specs found,
|
|
5
|
-
// each carrying its project NAME (so a consumer can resolve a name → spec).
|
|
6
|
-
//
|
|
7
|
-
// Recognition is location-bounded AND shape-confirmed (ADR-0017, narrowed):
|
|
8
|
-
// 1. <root>/.agents/spec/spec.md — repo-root single-project
|
|
9
|
-
// 2. <root>/.agents/specs/<project>/spec.md — repo-root multi-project
|
|
10
|
-
// 3. <project-path>/.agents/spec/spec.md — a nested project (** = project-path, any depth)
|
|
11
|
-
// 4. any extra anchor declared in .agents/sdd/spec-anchors.toml (ADR-0019) — opt-in and additive;
|
|
12
|
-
// absent config ⇒ only 1–3 are scanned (today's behavior). A pattern may carry a <project>
|
|
13
|
-
// capture token that both globs a segment and names the spec from it; `**` globs zero or more
|
|
14
|
-
// segments (any depth), for a spec whose depth under an anchor root varies.
|
|
15
|
-
// A spec.md at one of these locations is a spec only if its frontmatter `status` is in the
|
|
16
|
-
// lifecycle enum; a status-bearing spec.md elsewhere, or a stray spec.md at a spec location
|
|
17
|
-
// with no lifecycle status, is NOT loaded (so the scan never grabs the wrong file by accident).
|
|
18
|
-
//
|
|
19
|
-
// The project NAME cannot always be derived: it is `declared` (frontmatter `name`, authoritative),
|
|
20
|
-
// else `derived` (the repo-root single-project → `repo`; a `.agents/specs/<project>` folder → the
|
|
21
|
-
// folder), else `guessed` (a nested project's folder basename — may not be the name the user uses).
|
|
22
|
-
// `name-source` flags which, so a consumer knows when to confirm a guessed name.
|
|
23
|
-
//
|
|
24
|
-
// Pure functions are exported for node:test; running the file directly drives the CLI.
|
|
25
|
-
// No dependencies (the repo's node-≥23.6 / no-deps convention). --format json for a flat array;
|
|
26
|
-
// default output is TOON (the token-efficient tabular form the gateway scans). --resolve <name>
|
|
27
|
-
// filters to the exact name matches (0 rows = none, 1 = resolved, >1 = ambiguous).
|
|
28
|
-
|
|
29
|
-
import { existsSync, readdirSync, readFileSync, realpathSync } from 'node:fs'
|
|
30
|
-
import { join } from 'node:path'
|
|
31
|
-
import { pathToFileURL } from 'node:url'
|
|
32
|
-
|
|
33
|
-
export const LIFECYCLE_STATUSES = new Set(['draft', 'approved', 'implemented', 'deprecated'])
|
|
34
|
-
|
|
35
|
-
// The repo-root single-project (`.agents/spec`) has no folder to name it; this assumable label does.
|
|
36
|
-
const ROOT_PROJECT_NAME = 'repo'
|
|
37
|
-
|
|
38
|
-
// Dirs the scan never descends into (keep `.agents` — specs live under it).
|
|
39
|
-
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', '.turbo', '.next', 'coverage'])
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* A directory that is itself a checkout is a DIFFERENT repository's tree, not part of this corpus.
|
|
43
|
-
*
|
|
44
|
-
* A name blocklist cannot express this: it can only say "this is called X", never "this is a
|
|
45
|
-
* boundary". `SKIP_DIRS` already holds `.git`, so the walk skipped the METADATA directory while
|
|
46
|
-
* descending straight into the checkout that directory marks — one level off from where the guard
|
|
47
|
-
* was needed. Agent-harness worktree isolation checks this repo out inside itself, and the scan then
|
|
48
|
-
* found the whole corpus once per worktree: 38 spec files where 10 exist, and every corpus-wide
|
|
49
|
-
* guard silently auditing a tree nobody has.
|
|
50
|
-
*
|
|
51
|
-
* `.git` is a DIRECTORY in a clone and a FILE in a worktree or submodule, so both forms count.
|
|
52
|
-
*/
|
|
53
|
-
function isNestedCheckout(abs: string): boolean {
|
|
54
|
-
return existsSync(join(abs, '.git'))
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// The opt-in extra-anchor registry (ADR-0019). Scanned IN ADDITION TO the three fixed conventions;
|
|
58
|
-
// absent ⇒ only the fixed conventions are scanned (today's behavior, unchanged).
|
|
59
|
-
const ANCHORS_CONFIG = '.agents/sdd/spec-anchors.toml'
|
|
60
|
-
|
|
61
|
-
export type NameSource = 'declared' | 'derived' | 'guessed'
|
|
62
|
-
|
|
63
|
-
export interface SpecRecord {
|
|
64
|
-
/** Root-relative directory of the spec.md (its slug). */
|
|
65
|
-
path: string
|
|
66
|
-
/** The project name — declared in frontmatter, else derived, else guessed. */
|
|
67
|
-
name: string
|
|
68
|
-
/** Where `name` came from, so a consumer knows when to confirm a guess. */
|
|
69
|
-
nameSource: NameSource
|
|
70
|
-
status: string
|
|
71
|
-
/** Frontmatter project-path (the governed source dir), or '' when absent. */
|
|
72
|
-
projectPath: string
|
|
73
|
-
/** Gate verdicts as `<gate>:<verdict>` pairs, in spec→impl order; '' when none. */
|
|
74
|
-
approvals: string
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export interface Frontmatter {
|
|
78
|
-
status?: string
|
|
79
|
-
projectPath?: string
|
|
80
|
-
/** Declared project name (authoritative over derivation), when present. */
|
|
81
|
-
name?: string
|
|
82
|
-
/** gate → verdict (e.g. spec → approve). */
|
|
83
|
-
approval: Record<string, string>
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// ── Frontmatter parse (a minimal YAML subset — only the router-index schema) ──
|
|
87
|
-
// Extracts the leading `---` … `---` block and reads status, name, project-path, and each
|
|
88
|
-
// approval gate's verdict. Returns null when there is no frontmatter block at all.
|
|
89
|
-
export function parseFrontmatter(text: string): Frontmatter | null {
|
|
90
|
-
const m = /^---\r?\n([\s\S]*?)\r?\n---\s*(?:\r?\n|$)/.exec(text)
|
|
91
|
-
if (!m) return null
|
|
92
|
-
const fm: Frontmatter = { approval: {} }
|
|
93
|
-
let gate: string | null = null // current gate under `approval:`
|
|
94
|
-
for (const raw of m[1].split('\n')) {
|
|
95
|
-
const line = raw.replace(/\r$/, '')
|
|
96
|
-
if (line.trim() === '' || line.trim().startsWith('#')) continue
|
|
97
|
-
const indent = line.length - line.trimStart().length
|
|
98
|
-
const [key, ...rest] = line.trim().split(':')
|
|
99
|
-
const value = rest.join(':').trim()
|
|
100
|
-
if (indent === 0) {
|
|
101
|
-
gate = null
|
|
102
|
-
if (key === 'status') fm.status = unquote(value)
|
|
103
|
-
else if (key === 'project-path') fm.projectPath = unquote(value)
|
|
104
|
-
else if (key === 'name') fm.name = unquote(value)
|
|
105
|
-
else if (key === 'approval') gate = null // enter the approval block
|
|
106
|
-
} else if (indent === 2 && value === '' && insideApproval(m[1], line)) {
|
|
107
|
-
gate = key // a gate name under approval:
|
|
108
|
-
} else if (indent >= 4 && key === 'verdict' && gate) {
|
|
109
|
-
fm.approval[gate] = unquote(value)
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
return fm
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// True when `line` sits under the top-level `approval:` key (the nearest indent-0 parent is it).
|
|
116
|
-
function insideApproval(block: string, line: string): boolean {
|
|
117
|
-
const lines = block.split('\n')
|
|
118
|
-
const idx = lines.indexOf(line)
|
|
119
|
-
for (let i = idx - 1; i >= 0; i--) {
|
|
120
|
-
const l = lines[i].replace(/\r$/, '')
|
|
121
|
-
if (l.trim() === '' || l.trim().startsWith('#')) continue
|
|
122
|
-
if (l.length - l.trimStart().length === 0) return l.trim().replace(/:.*$/, '') === 'approval'
|
|
123
|
-
}
|
|
124
|
-
return false
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function unquote(v: string): string {
|
|
128
|
-
return v.replace(/^["']|["']$/g, '')
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// ── Location recognition ──
|
|
132
|
-
// Classify a root-relative spec.md path to one of the three spec locations, returning the
|
|
133
|
-
// pattern + the location dir it implies (the <project> folder for root-multi, the project-path
|
|
134
|
-
// for nested, '' for root-single). Returns null for any other location.
|
|
135
|
-
type LocationPattern = 'root-single' | 'root-multi' | 'nested' | 'extra'
|
|
136
|
-
|
|
137
|
-
export interface Location {
|
|
138
|
-
pattern: LocationPattern
|
|
139
|
-
locationDir: string
|
|
140
|
-
/** For an extra anchor whose pattern carried a <project> token: the captured name segment. */
|
|
141
|
-
capturedName?: string
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export function classifyLocation(relPath: string): Location | null {
|
|
145
|
-
const p = relPath.replace(/\\/g, '/')
|
|
146
|
-
if (p === '.agents/spec/spec.md') return { pattern: 'root-single', locationDir: '' }
|
|
147
|
-
const multi = /^\.agents\/specs\/([^/]+)\/spec\.md$/.exec(p)
|
|
148
|
-
if (multi) return { pattern: 'root-multi', locationDir: multi[1] }
|
|
149
|
-
const nested = /^(.+)\/\.agents\/spec\/spec\.md$/.exec(p)
|
|
150
|
-
if (nested) return { pattern: 'nested', locationDir: nested[1] }
|
|
151
|
-
return null
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Derive a project's name + the confidence in it. A declared frontmatter `name` is authoritative;
|
|
155
|
-
// otherwise the repo-root single-project is the assumable `repo`, a `.agents/specs/<project>` folder
|
|
156
|
-
// names itself (derived), and a nested project falls back to its folder basename (guessed — it may
|
|
157
|
-
// not be the name the user uses; a consumer should confirm).
|
|
158
|
-
export function deriveName(loc: Location, fm: Frontmatter): { name: string; nameSource: NameSource } {
|
|
159
|
-
if (fm.name) return { name: fm.name, nameSource: 'declared' }
|
|
160
|
-
if (loc.pattern === 'root-single') return { name: ROOT_PROJECT_NAME, nameSource: 'derived' }
|
|
161
|
-
if (loc.pattern === 'root-multi') return { name: loc.locationDir, nameSource: 'derived' }
|
|
162
|
-
// An extra anchor with a <project> capture names the spec from the captured segment (derived);
|
|
163
|
-
// without a capture it falls back to the folder basename (guessed), like a nested project.
|
|
164
|
-
if (loc.pattern === 'extra' && loc.capturedName) {
|
|
165
|
-
return { name: loc.capturedName, nameSource: 'derived' }
|
|
166
|
-
}
|
|
167
|
-
return { name: loc.locationDir.split('/').pop() ?? loc.locationDir, nameSource: 'guessed' }
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// ── Scan ──
|
|
171
|
-
// Walk the tree under root, returning root-relative paths of every spec.md sitting at one of
|
|
172
|
-
// the three spec locations. The walk finds `.agents` dirs at any depth (pattern 3) and probes
|
|
173
|
-
// `spec/spec.md` (patterns 1 & 3) plus, only at the repo root, `specs/<project>/spec.md`
|
|
174
|
-
// (pattern 2).
|
|
175
|
-
export function discoverSpecFiles(root: string): string[] {
|
|
176
|
-
const found: string[] = []
|
|
177
|
-
const walk = (relDir: string): void => {
|
|
178
|
-
let entries: import('node:fs').Dirent[]
|
|
179
|
-
try {
|
|
180
|
-
entries = readdirSync(join(root, relDir), { withFileTypes: true })
|
|
181
|
-
} catch {
|
|
182
|
-
return
|
|
183
|
-
}
|
|
184
|
-
for (const e of entries) {
|
|
185
|
-
if (!e.isDirectory() || SKIP_DIRS.has(e.name)) continue
|
|
186
|
-
const childRel = relDir ? `${relDir}/${e.name}` : e.name
|
|
187
|
-
if (isNestedCheckout(join(root, childRel))) continue
|
|
188
|
-
if (e.name === '.agents') {
|
|
189
|
-
probeAgents(root, childRel, found)
|
|
190
|
-
continue // spec locations live directly under .agents, no deeper walk needed
|
|
191
|
-
}
|
|
192
|
-
walk(childRel)
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
walk('')
|
|
196
|
-
return found
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Probe a discovered `.agents` dir for the spec locations beneath it.
|
|
200
|
-
function probeAgents(root: string, agentsRel: string, found: string[]): void {
|
|
201
|
-
const single = `${agentsRel}/spec/spec.md`
|
|
202
|
-
if (existsSync(join(root, single))) found.push(single)
|
|
203
|
-
if (agentsRel === '.agents') {
|
|
204
|
-
// pattern 2 — repo-root multi-project — is root-only (no ** prefix).
|
|
205
|
-
const specsDir = join(root, '.agents', 'specs')
|
|
206
|
-
let projects: import('node:fs').Dirent[]
|
|
207
|
-
try {
|
|
208
|
-
projects = readdirSync(specsDir, { withFileTypes: true })
|
|
209
|
-
} catch {
|
|
210
|
-
projects = []
|
|
211
|
-
}
|
|
212
|
-
for (const p of projects) {
|
|
213
|
-
if (!p.isDirectory()) continue
|
|
214
|
-
const rel = `.agents/specs/${p.name}/spec.md`
|
|
215
|
-
if (existsSync(join(root, rel))) found.push(rel)
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// ── Extra anchors — the opt-in registry (ADR-0019) ──
|
|
221
|
-
// A minimal TOML read: pull the string entries out of the `anchors = [ … ]` array. Paths carry no
|
|
222
|
-
// `#`, so entries are the quoted strings inside the array literal (across newlines). Returns [] when
|
|
223
|
-
// there is no anchors array (a config that commented them all out is a valid empty set).
|
|
224
|
-
export function parseAnchorsToml(text: string): string[] {
|
|
225
|
-
const m = /(^|\n)\s*anchors\s*=\s*\[([\s\S]*?)\]/.exec(text)
|
|
226
|
-
if (!m) return []
|
|
227
|
-
const out: string[] = []
|
|
228
|
-
for (const q of m[2].matchAll(/"([^"]*)"|'([^']*)'/g)) out.push((q[1] ?? q[2]).trim())
|
|
229
|
-
return out.filter((s) => s !== '')
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Read the extra-anchor patterns, FAIL-SAFE: an absent config yields []; an unreadable/malformed one
|
|
233
|
-
// warns and yields [] so the gateway's status scan never crashes on a hand-corrupted config.
|
|
234
|
-
export function readAnchors(root: string): string[] {
|
|
235
|
-
const file = join(root, ANCHORS_CONFIG)
|
|
236
|
-
if (!existsSync(file)) return []
|
|
237
|
-
try {
|
|
238
|
-
return parseAnchorsToml(readFileSync(file, 'utf8'))
|
|
239
|
-
} catch {
|
|
240
|
-
process.stderr.write(`discover-specs: ignoring unreadable ${ANCHORS_CONFIG}\n`)
|
|
241
|
-
return []
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Every dir reachable from `startDir` by descending zero or more levels (startDir itself included),
|
|
246
|
-
// skipping SKIP_DIRS. Backs the `**` segment (any-depth glob) below.
|
|
247
|
-
function collectDescendants(root: string, startDir: string): string[] {
|
|
248
|
-
const out = [startDir]
|
|
249
|
-
let entries: import('node:fs').Dirent[]
|
|
250
|
-
try {
|
|
251
|
-
entries = readdirSync(join(root, startDir), { withFileTypes: true })
|
|
252
|
-
} catch {
|
|
253
|
-
return out
|
|
254
|
-
}
|
|
255
|
-
for (const e of entries) {
|
|
256
|
-
if (!e.isDirectory() || SKIP_DIRS.has(e.name)) continue
|
|
257
|
-
const childRel = startDir ? `${startDir}/${e.name}` : e.name
|
|
258
|
-
if (isNestedCheckout(join(root, childRel))) continue
|
|
259
|
-
out.push(...collectDescendants(root, childRel))
|
|
260
|
-
}
|
|
261
|
-
return out
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Expand one anchor pattern against the filesystem into the spec dirs it matches. A `*` segment globs
|
|
265
|
-
// one directory level; a `**` segment globs zero or more levels (any depth); a `<project>` segment
|
|
266
|
-
// globs AND captures one level as the spec name. A literal segment must exist. The matched dir is a
|
|
267
|
-
// spec dir iff it holds a spec.md.
|
|
268
|
-
export function expandAnchor(root: string, pattern: string): { rel: string; capturedName?: string }[] {
|
|
269
|
-
const segs = pattern
|
|
270
|
-
.replace(/\\/g, '/')
|
|
271
|
-
.replace(/^\/+|\/+$/g, '')
|
|
272
|
-
.split('/')
|
|
273
|
-
.filter(Boolean)
|
|
274
|
-
let frontier: { dir: string; capturedName?: string }[] = [{ dir: '' }]
|
|
275
|
-
for (const seg of segs) {
|
|
276
|
-
const next: { dir: string; capturedName?: string }[] = []
|
|
277
|
-
if (seg === '**') {
|
|
278
|
-
for (const node of frontier) {
|
|
279
|
-
for (const dir of collectDescendants(root, node.dir)) next.push({ dir, capturedName: node.capturedName })
|
|
280
|
-
}
|
|
281
|
-
frontier = next
|
|
282
|
-
continue
|
|
283
|
-
}
|
|
284
|
-
const isGlob = seg === '*' || seg === '<project>'
|
|
285
|
-
for (const node of frontier) {
|
|
286
|
-
if (isGlob) {
|
|
287
|
-
let entries: import('node:fs').Dirent[]
|
|
288
|
-
try {
|
|
289
|
-
entries = readdirSync(join(root, node.dir), { withFileTypes: true })
|
|
290
|
-
} catch {
|
|
291
|
-
continue
|
|
292
|
-
}
|
|
293
|
-
for (const e of entries) {
|
|
294
|
-
if (!e.isDirectory() || SKIP_DIRS.has(e.name)) continue
|
|
295
|
-
const childRel = node.dir ? `${node.dir}/${e.name}` : e.name
|
|
296
|
-
if (isNestedCheckout(join(root, childRel))) continue
|
|
297
|
-
next.push({
|
|
298
|
-
dir: childRel,
|
|
299
|
-
capturedName: seg === '<project>' ? e.name : node.capturedName,
|
|
300
|
-
})
|
|
301
|
-
}
|
|
302
|
-
} else {
|
|
303
|
-
next.push({ dir: node.dir ? `${node.dir}/${seg}` : seg, capturedName: node.capturedName })
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
frontier = next
|
|
307
|
-
}
|
|
308
|
-
const out: { rel: string; capturedName?: string }[] = []
|
|
309
|
-
for (const node of frontier) {
|
|
310
|
-
const rel = node.dir ? `${node.dir}/spec.md` : 'spec.md'
|
|
311
|
-
if (existsSync(join(root, rel))) out.push({ rel, capturedName: node.capturedName })
|
|
312
|
-
}
|
|
313
|
-
return out
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// ── Collect ──
|
|
317
|
-
// Build a SpecRecord from a spec.md at `rel` under `loc`, or null when it fails the status shape
|
|
318
|
-
// filter (or the file is unreadable). Shared by the fixed-convention and extra-anchor passes.
|
|
319
|
-
function recordFor(root: string, rel: string, loc: Location): SpecRecord | null {
|
|
320
|
-
let fm: Frontmatter | null
|
|
321
|
-
try {
|
|
322
|
-
fm = parseFrontmatter(readFileSync(join(root, rel), 'utf8'))
|
|
323
|
-
} catch {
|
|
324
|
-
return null
|
|
325
|
-
}
|
|
326
|
-
if (!fm?.status || !LIFECYCLE_STATUSES.has(fm.status)) return null // shape filter
|
|
327
|
-
const { name, nameSource } = deriveName(loc, fm)
|
|
328
|
-
return {
|
|
329
|
-
path: rel.replace(/\/spec\.md$/, ''),
|
|
330
|
-
name,
|
|
331
|
-
nameSource,
|
|
332
|
-
status: fm.status,
|
|
333
|
-
projectPath: fm.projectPath ?? '',
|
|
334
|
-
approvals: Object.entries(fm.approval)
|
|
335
|
-
.map(([gate, verdict]) => `${gate}:${verdict}`)
|
|
336
|
-
.join(';'),
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// The list of specs under root: every spec.md at one of the three fixed conventions PLUS every
|
|
341
|
-
// spec.md at a declared extra anchor, whose frontmatter status is in the lifecycle enum, keyed by
|
|
342
|
-
// its folder slug, sorted by path. Extra anchors are additive and deduped against the fixed set.
|
|
343
|
-
export function collectSpecs(root: string): SpecRecord[] {
|
|
344
|
-
const out: SpecRecord[] = []
|
|
345
|
-
const seen = new Set<string>()
|
|
346
|
-
for (const rel of discoverSpecFiles(root)) {
|
|
347
|
-
const loc = classifyLocation(rel)
|
|
348
|
-
if (!loc) continue
|
|
349
|
-
const rec = recordFor(root, rel, loc)
|
|
350
|
-
if (rec) {
|
|
351
|
-
out.push(rec)
|
|
352
|
-
seen.add(rel)
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
for (const pattern of readAnchors(root)) {
|
|
356
|
-
for (const { rel, capturedName } of expandAnchor(root, pattern)) {
|
|
357
|
-
if (seen.has(rel)) continue // already found at a fixed convention
|
|
358
|
-
const rec = recordFor(root, rel, {
|
|
359
|
-
pattern: 'extra',
|
|
360
|
-
locationDir: rel.replace(/\/spec\.md$/, ''),
|
|
361
|
-
capturedName,
|
|
362
|
-
})
|
|
363
|
-
if (rec) {
|
|
364
|
-
out.push(rec)
|
|
365
|
-
seen.add(rel)
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
return out.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0))
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// ── Resolve a name (the deterministic half of resolve-a-name) ──
|
|
373
|
-
// Exact, case-insensitive name match over the discovered list. `match` resolves to one spec;
|
|
374
|
-
// `ambiguous` returns the candidate set for a consumer to disambiguate WITH THE USER (the agentic
|
|
375
|
-
// half, not this function's job); `none` when no name matches.
|
|
376
|
-
export type ResolveResult =
|
|
377
|
-
| { kind: 'match'; spec: SpecRecord }
|
|
378
|
-
| { kind: 'ambiguous'; candidates: SpecRecord[] }
|
|
379
|
-
| { kind: 'none' }
|
|
380
|
-
|
|
381
|
-
export function resolveByName(specs: SpecRecord[], name: string): ResolveResult {
|
|
382
|
-
const want = name.trim().toLowerCase()
|
|
383
|
-
const hits = specs.filter((s) => s.name.toLowerCase() === want)
|
|
384
|
-
if (hits.length === 0) return { kind: 'none' }
|
|
385
|
-
if (hits.length === 1) return { kind: 'match', spec: hits[0] }
|
|
386
|
-
return { kind: 'ambiguous', candidates: hits }
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// ── Output ──
|
|
390
|
-
const COLUMNS = ['path', 'name', 'nameSource', 'status', 'projectPath', 'approvals'] as const
|
|
391
|
-
|
|
392
|
-
// Quote a TOON field only when it carries the delimiter, a quote, or edge whitespace.
|
|
393
|
-
function toonField(v: string): string {
|
|
394
|
-
if (v === '' || /[",]/.test(v) || v !== v.trim()) return `"${v.replace(/"/g, '""')}"`
|
|
395
|
-
return v
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
export function toToon(specs: SpecRecord[]): string {
|
|
399
|
-
const header = `specs[${specs.length}]{${COLUMNS.join(',')}}:`
|
|
400
|
-
const rows = specs.map((s) => ` ${COLUMNS.map((c) => toonField(s[c])).join(',')}`)
|
|
401
|
-
return [header, ...rows].join('\n')
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
export function main(argv: string[]): number {
|
|
405
|
-
const root = argv.includes('--root') ? (argv[argv.indexOf('--root') + 1] ?? '.') : '.'
|
|
406
|
-
const format = argv.includes('--format') ? argv[argv.indexOf('--format') + 1] : 'toon'
|
|
407
|
-
let specs = collectSpecs(root)
|
|
408
|
-
if (argv.includes('--resolve')) {
|
|
409
|
-
const name = argv[argv.indexOf('--resolve') + 1] ?? ''
|
|
410
|
-
const r = resolveByName(specs, name)
|
|
411
|
-
specs = r.kind === 'match' ? [r.spec] : r.kind === 'ambiguous' ? r.candidates : []
|
|
412
|
-
}
|
|
413
|
-
const out = format === 'json' ? JSON.stringify(specs, null, 2) : toToon(specs)
|
|
414
|
-
process.stdout.write(`${out}\n`)
|
|
415
|
-
return 0
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
419
|
-
process.exit(main(process.argv.slice(2)))
|
|
420
|
-
}
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
5
|
-
import { pathToFileURL } from "node:url";
|
|
6
|
-
//#region skills/init/scripts/wire-statusline.mts
|
|
7
|
-
const STATUS_FILE = ".agents/sdd/statusline";
|
|
8
|
-
const SETTINGS_FILE = ".claude/settings.json";
|
|
9
|
-
const GITIGNORE_FILE = ".gitignore";
|
|
10
|
-
const GLOBAL_SETTINGS_FILE = join(homedir(), ".claude", "settings.json");
|
|
11
|
-
const MARKER_BEGIN = "# sdd-statusline:begin";
|
|
12
|
-
const MARKER_END = "# sdd-statusline:end";
|
|
13
|
-
const MODE_PREFIX = "# sdd-statusline:mode:";
|
|
14
|
-
const ORIG_OPEN = "__sdd_orig() {";
|
|
15
|
-
const ORIG_CLOSE = "}";
|
|
16
|
-
const NO_BASE = ":";
|
|
17
|
-
function buildCommand(base, mode) {
|
|
18
|
-
const origBody = base && base.trim() !== "" ? base : NO_BASE;
|
|
19
|
-
const render = mode === "own-line" ? `if [ -s "$__sdd_file" ]; then\n __sdd_val="$(cat "$__sdd_file")"\n printf '%s\\n%s\\n' "$__sdd_base" "$__sdd_val"\nelse\n printf '%s\\n' "$__sdd_base"\nfi` : `if [ -s "$__sdd_file" ]; then\n __sdd_val="$(cat "$__sdd_file")"\n printf '%s | %s\\n' "$__sdd_base" "$__sdd_val"\nelse\n printf '%s\\n' "$__sdd_base"\nfi`;
|
|
20
|
-
return [
|
|
21
|
-
MARKER_BEGIN,
|
|
22
|
-
`${MODE_PREFIX}${mode}`,
|
|
23
|
-
ORIG_OPEN,
|
|
24
|
-
origBody,
|
|
25
|
-
ORIG_CLOSE,
|
|
26
|
-
"__sdd_base=\"$(__sdd_orig 2>/dev/null)\"",
|
|
27
|
-
`__sdd_file="${STATUS_FILE}"`,
|
|
28
|
-
render,
|
|
29
|
-
MARKER_END
|
|
30
|
-
].join("\n");
|
|
31
|
-
}
|
|
32
|
-
function parseCommand(command) {
|
|
33
|
-
if (!command?.includes(MARKER_BEGIN)) return { wired: false };
|
|
34
|
-
const modeMatch = new RegExp(`${MODE_PREFIX}(own-line|same-line)`).exec(command);
|
|
35
|
-
const origMatch = /__sdd_orig\(\) \{\n([\s\S]*?)\n\}/.exec(command);
|
|
36
|
-
const mode = modeMatch?.[1] ?? "own-line";
|
|
37
|
-
const origBody = origMatch?.[1] ?? NO_BASE;
|
|
38
|
-
return {
|
|
39
|
-
wired: true,
|
|
40
|
-
base: origBody === NO_BASE ? void 0 : origBody,
|
|
41
|
-
mode
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
function baseToWrap(existingCommand) {
|
|
45
|
-
const parsed = parseCommand(existingCommand);
|
|
46
|
-
return parsed.wired ? parsed.base : existingCommand;
|
|
47
|
-
}
|
|
48
|
-
function readSettingsFile(file) {
|
|
49
|
-
if (!existsSync(file)) return {};
|
|
50
|
-
try {
|
|
51
|
-
return JSON.parse(readFileSync(file, "utf8"));
|
|
52
|
-
} catch {
|
|
53
|
-
return {};
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
function readSettings(root) {
|
|
57
|
-
return readSettingsFile(join(root, SETTINGS_FILE));
|
|
58
|
-
}
|
|
59
|
-
function readStatusLineCommand(file) {
|
|
60
|
-
const command = readSettingsFile(file).statusLine?.command;
|
|
61
|
-
if (typeof command !== "string" || command.trim() === "") return void 0;
|
|
62
|
-
return command;
|
|
63
|
-
}
|
|
64
|
-
function writeSettings(root, settings) {
|
|
65
|
-
const file = join(root, SETTINGS_FILE);
|
|
66
|
-
mkdirSync(dirname(file), { recursive: true });
|
|
67
|
-
writeFileSync(file, `${JSON.stringify(settings, null, 2)}\n`);
|
|
68
|
-
}
|
|
69
|
-
function composeStatusLine(root, mode, globalCommand) {
|
|
70
|
-
const settings = readSettings(root);
|
|
71
|
-
const existing = settings.statusLine?.command;
|
|
72
|
-
const hasProjectStatusLine = typeof existing === "string" && existing.trim() !== "";
|
|
73
|
-
const globalBaseComposed = !hasProjectStatusLine && globalCommand !== void 0;
|
|
74
|
-
const command = buildCommand(hasProjectStatusLine ? baseToWrap(existing) : globalCommand, mode);
|
|
75
|
-
if (existing === command) return {
|
|
76
|
-
changed: false,
|
|
77
|
-
command,
|
|
78
|
-
globalBaseComposed: false
|
|
79
|
-
};
|
|
80
|
-
settings.statusLine = {
|
|
81
|
-
...settings.statusLine,
|
|
82
|
-
type: "command",
|
|
83
|
-
command
|
|
84
|
-
};
|
|
85
|
-
writeSettings(root, settings);
|
|
86
|
-
return {
|
|
87
|
-
changed: true,
|
|
88
|
-
command,
|
|
89
|
-
globalBaseComposed
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
function isGitRepo(root) {
|
|
93
|
-
return existsSync(join(root, ".git"));
|
|
94
|
-
}
|
|
95
|
-
function splitLines(text) {
|
|
96
|
-
const lines = text.split("\n");
|
|
97
|
-
if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
98
|
-
return lines;
|
|
99
|
-
}
|
|
100
|
-
function addGitignoreEntry(root) {
|
|
101
|
-
const file = join(root, GITIGNORE_FILE);
|
|
102
|
-
const lines = existsSync(file) ? splitLines(readFileSync(file, "utf8")) : [];
|
|
103
|
-
if (lines.some((l) => l.trim() === ".agents/sdd/statusline")) return { changed: false };
|
|
104
|
-
const next = [...lines, STATUS_FILE];
|
|
105
|
-
writeFileSync(file, `${next.join("\n")}\n`);
|
|
106
|
-
return { changed: true };
|
|
107
|
-
}
|
|
108
|
-
function detectStatusLines(root, globalSettingsFile) {
|
|
109
|
-
const projectCommand = readStatusLineCommand(join(root, SETTINGS_FILE));
|
|
110
|
-
const project = projectCommand === void 0 ? "absent" : parseCommand(projectCommand).wired ? "wired" : "foreign";
|
|
111
|
-
const global = readStatusLineCommand(globalSettingsFile ?? GLOBAL_SETTINGS_FILE) !== void 0 ? "present" : "absent";
|
|
112
|
-
return {
|
|
113
|
-
project,
|
|
114
|
-
global,
|
|
115
|
-
shadow: project === "absent" && global === "present"
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
function wireStatusline(root, mode, opts) {
|
|
119
|
-
return {
|
|
120
|
-
settings: composeStatusLine(root, mode, opts?.globalBase ?? true ? readStatusLineCommand(opts?.globalSettingsFile ?? GLOBAL_SETTINGS_FILE) : void 0),
|
|
121
|
-
gitignore: isGitRepo(root) ? {
|
|
122
|
-
...addGitignoreEntry(root),
|
|
123
|
-
skipped: false
|
|
124
|
-
} : {
|
|
125
|
-
changed: false,
|
|
126
|
-
skipped: true
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
function flag(argv, name) {
|
|
131
|
-
const i = argv.indexOf(name);
|
|
132
|
-
return i === -1 ? void 0 : argv[i + 1];
|
|
133
|
-
}
|
|
134
|
-
function main(argv) {
|
|
135
|
-
const root = flag(argv, "--root") ?? ".";
|
|
136
|
-
const globalSettingsFile = flag(argv, "--global-settings");
|
|
137
|
-
const w = (s) => process.stdout.write(`${s}\n`);
|
|
138
|
-
if (argv.includes("--detect")) {
|
|
139
|
-
const result = detectStatusLines(root, globalSettingsFile);
|
|
140
|
-
w(`project statusLine: ${result.project}`);
|
|
141
|
-
w(`global statusLine: ${result.global}`);
|
|
142
|
-
w(result.shadow ? "shadow risk: yes — wiring would shadow the global statusline; compose it as the base (default) or pass --no-global-base to shadow deliberately" : "shadow risk: no");
|
|
143
|
-
return 0;
|
|
144
|
-
}
|
|
145
|
-
if (argv.includes("--wire")) {
|
|
146
|
-
const mode = flag(argv, "--mode");
|
|
147
|
-
if (mode !== "own-line" && mode !== "same-line") {
|
|
148
|
-
w("refused: --mode must be own-line or same-line");
|
|
149
|
-
return 1;
|
|
150
|
-
}
|
|
151
|
-
const result = wireStatusline(root, mode, {
|
|
152
|
-
globalSettingsFile,
|
|
153
|
-
globalBase: !argv.includes("--no-global-base")
|
|
154
|
-
});
|
|
155
|
-
w(result.settings.changed ? `wired statusLine (${mode})` : "statusLine already wired (unchanged)");
|
|
156
|
-
if (result.settings.globalBaseComposed) w("composed the global statusLine command as the wrapped base");
|
|
157
|
-
if (result.gitignore.skipped) w("not a git repo — gitignore skipped");
|
|
158
|
-
else w(result.gitignore.changed ? `added ${STATUS_FILE} to .gitignore` : ".gitignore already up to date");
|
|
159
|
-
return 0;
|
|
160
|
-
}
|
|
161
|
-
w("usage: wire-statusline --root <dir> --wire --mode own-line|same-line [--global-settings <file>] [--no-global-base]\n wire-statusline --root <dir> --detect [--global-settings <file>]");
|
|
162
|
-
return 0;
|
|
163
|
-
}
|
|
164
|
-
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) process.exit(main(process.argv.slice(2)));
|
|
165
|
-
//#endregion
|
|
166
|
-
export { GITIGNORE_FILE, GLOBAL_SETTINGS_FILE, SETTINGS_FILE, STATUS_FILE, addGitignoreEntry, buildCommand, composeStatusLine, detectStatusLines, isGitRepo, main, parseCommand, readStatusLineCommand, wireStatusline };
|