@hegemonart/get-design-done 1.31.0 → 1.32.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +75 -0
- package/NOTICE +262 -0
- package/README.md +13 -1
- package/SKILL.md +4 -0
- package/agents/design-authority-watcher.md +1 -1
- package/agents/perf-analyzer.md +2 -2
- package/bin/gdd-mcp +78 -0
- package/bin/gdd-sdk +34 -24
- package/bin/gdd-state-mcp +78 -0
- package/{README.de.md → docs/i18n/README.de.md} +1 -1
- package/{README.fr.md → docs/i18n/README.fr.md} +1 -1
- package/{README.it.md → docs/i18n/README.it.md} +1 -1
- package/{README.ja.md → docs/i18n/README.ja.md} +1 -1
- package/{README.ko.md → docs/i18n/README.ko.md} +1 -1
- package/{README.zh-CN.md → docs/i18n/README.zh-CN.md} +1 -1
- package/hooks/_hook-emit.js +1 -1
- package/hooks/budget-enforcer.ts +5 -5
- package/hooks/context-exhaustion.ts +2 -2
- package/hooks/gdd-precompact-snapshot.js +3 -3
- package/hooks/gdd-read-injection-scanner.ts +2 -2
- package/hooks/gdd-sessionstart-recap.js +1 -1
- package/hooks/gdd-turn-closeout.js +1 -1
- package/hooks/hooks.json +9 -0
- package/hooks/inject-using-gdd.sh +72 -0
- package/hooks/run-hook.cmd +35 -0
- package/package.json +20 -9
- package/recipes/.gitkeep +0 -0
- package/reference/schemas/events.schema.json +63 -1
- package/reference/schemas/recipe.schema.json +33 -0
- package/scripts/cli/gdd-events.mjs +5 -5
- package/scripts/lib/cache/gdd-cache-manager.cjs +1 -1
- package/scripts/lib/cli/index.ts +22 -160
- package/scripts/lib/connection-probe/index.cjs +1 -1
- package/scripts/lib/discuss-parallel-runner/aggregator.ts +1 -1
- package/scripts/lib/discuss-parallel-runner/index.ts +1 -1
- package/scripts/lib/error-classifier.cjs +24 -227
- package/scripts/lib/event-stream/index.ts +25 -193
- package/scripts/lib/gdd-errors/index.ts +24 -213
- package/scripts/lib/gdd-state/index.ts +23 -161
- package/scripts/lib/health-mirror/index.cjs +79 -1
- package/scripts/lib/iteration-budget.cjs +23 -199
- package/scripts/lib/jittered-backoff.cjs +24 -107
- package/scripts/lib/lockfile.cjs +23 -195
- package/scripts/lib/logger/index.ts +1 -1
- package/scripts/lib/parallelism-engine/concurrency-tuner.cjs +1 -1
- package/scripts/lib/perf-analyzer/index.cjs +1 -1
- package/scripts/lib/pipeline-runner/index.ts +4 -4
- package/scripts/lib/pipeline-runner/state-machine.ts +1 -1
- package/scripts/lib/prompt-dedup/index.cjs +1 -1
- package/scripts/lib/rate-guard.cjs +2 -2
- package/scripts/lib/recipe-loader.cjs +142 -0
- package/scripts/lib/session-runner/errors.ts +3 -3
- package/scripts/lib/session-runner/index.ts +3 -3
- package/scripts/lib/session-runner/transcript.ts +1 -1
- package/scripts/lib/tool-scoping/index.ts +1 -1
- package/scripts/mcp-servers/gdd-mcp/server.ts +29 -311
- package/scripts/mcp-servers/gdd-state/server.ts +28 -282
- package/sdk/README.md +45 -0
- package/{scripts/lib → sdk}/cli/commands/audit.ts +3 -3
- package/{scripts/lib → sdk}/cli/commands/init.ts +3 -3
- package/{scripts/lib → sdk}/cli/commands/query.ts +4 -4
- package/{scripts/lib → sdk}/cli/commands/run.ts +5 -5
- package/{scripts/lib → sdk}/cli/commands/stage.ts +5 -5
- package/sdk/cli/index.js +8091 -0
- package/sdk/cli/index.ts +172 -0
- package/{scripts/lib → sdk}/cli/parse-args.ts +2 -2
- package/{scripts/lib/gdd-errors → sdk/errors}/classification.ts +1 -1
- package/sdk/errors/index.ts +218 -0
- package/{scripts/lib → sdk}/event-stream/emitter.ts +1 -1
- package/sdk/event-stream/index.ts +197 -0
- package/{scripts/lib → sdk}/event-stream/reader.ts +1 -1
- package/{scripts/lib → sdk}/event-stream/types.ts +2 -2
- package/{scripts/lib → sdk}/event-stream/writer.ts +1 -1
- package/sdk/index.ts +19 -0
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/README.md +3 -3
- package/sdk/mcp/gdd-mcp/server.js +1966 -0
- package/sdk/mcp/gdd-mcp/server.ts +325 -0
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_cycle_recap.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_decisions_list.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_events_tail.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_health.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_intel_get.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_learnings_digest.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_phase_current.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_phases_list.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_plans_list.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_reflections_latest.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_status.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_telemetry_query.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/index.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/shared.ts +3 -3
- package/sdk/mcp/gdd-state/server.js +2790 -0
- package/sdk/mcp/gdd-state/server.ts +294 -0
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_blocker.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_decision.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_must_have.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/checkpoint.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/frontmatter_update.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/get.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/index.ts +1 -1
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/probe_connections.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/resolve_blocker.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/set_status.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/shared.ts +8 -8
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/transition_stage.ts +4 -4
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/update_progress.ts +2 -2
- package/sdk/primitives/error-classifier.cjs +232 -0
- package/sdk/primitives/iteration-budget.cjs +205 -0
- package/sdk/primitives/jittered-backoff.cjs +112 -0
- package/sdk/primitives/lockfile.cjs +201 -0
- package/{scripts/lib/gdd-state → sdk/state}/gates.ts +1 -1
- package/sdk/state/index.ts +167 -0
- package/{scripts/lib/gdd-state → sdk/state}/lockfile.ts +1 -1
- package/{scripts/lib/gdd-state → sdk/state}/mutator.ts +1 -1
- package/{scripts/lib/gdd-state → sdk/state}/parser.ts +1 -1
- package/{scripts/lib/gdd-state → sdk/state}/types.ts +4 -4
- package/skills/audit/SKILL.md +13 -0
- package/skills/brief/SKILL.md +25 -0
- package/skills/design/SKILL.md +17 -0
- package/skills/discuss/SKILL.md +13 -0
- package/skills/explore/SKILL.md +17 -0
- package/skills/health/SKILL.md +6 -0
- package/skills/plan/SKILL.md +25 -0
- package/skills/quality-gate/SKILL.md +2 -2
- package/skills/router/SKILL.md +4 -0
- package/skills/router/router-pick-emitter.md +78 -0
- package/skills/using-gdd/SKILL.md +78 -0
- package/skills/verify/SKILL.md +17 -0
- package/scripts/aggregate-agent-metrics.ts +0 -282
- package/scripts/bootstrap-manifest.txt +0 -3
- package/scripts/bootstrap.sh +0 -80
- package/scripts/build-distribution-bundles.cjs +0 -549
- package/scripts/build-intel.cjs +0 -486
- package/scripts/codegen-schema-types.ts +0 -149
- package/scripts/detect-stale-refs.cjs +0 -107
- package/scripts/e2e/run-headless.ts +0 -514
- package/scripts/extract-changelog-section.cjs +0 -58
- package/scripts/gsd-cleanup-incubator.cjs +0 -367
- package/scripts/injection-patterns.cjs +0 -58
- package/scripts/lint-agentskills-spec.cjs +0 -457
- package/scripts/release-smoke-test.cjs +0 -200
- package/scripts/rollback-release.sh +0 -42
- package/scripts/run-injection-scanner-ci.cjs +0 -83
- package/scripts/tests/test-authority-rejected-kinds.sh +0 -58
- package/scripts/tests/test-authority-watcher-diff.sh +0 -113
- package/scripts/tests/test-motion-provenance.sh +0 -64
- package/scripts/validate-frontmatter.ts +0 -409
- package/scripts/validate-incubator-scope.cjs +0 -133
- package/scripts/validate-schemas.ts +0 -401
- package/scripts/validate-skill-length.cjs +0 -283
- package/scripts/verify-version-sync.cjs +0 -30
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_cycle_recap.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_decisions_list.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_events_tail.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_health.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_intel_get.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_learnings_digest.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_phase_current.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_phases_list.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_plans_list.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_reflections_latest.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_status.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_telemetry_query.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_blocker.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_decision.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_must_have.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/checkpoint.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/frontmatter_update.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/get.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/probe_connections.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/resolve_blocker.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/set_status.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/transition_stage.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/update_progress.schema.json +0 -0
- /package/{scripts/lib → sdk/primitives}/error-classifier.d.cts +0 -0
- /package/{scripts/lib → sdk/primitives}/iteration-budget.d.cts +0 -0
- /package/{scripts/lib → sdk/primitives}/jittered-backoff.d.cts +0 -0
- /package/{scripts/lib → sdk/primitives}/lockfile.d.cts +0 -0
|
@@ -1,401 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* validate-schemas.ts
|
|
4
|
-
*
|
|
5
|
-
* Runs all Draft-07 JSON schemas under reference/schemas/ against their
|
|
6
|
-
* corresponding subject files. Used by `npm run validate:schemas` and by the
|
|
7
|
-
* CI validate job.
|
|
8
|
-
*
|
|
9
|
-
* Primary path: spawn `npx --yes ajv-cli@5 validate -s <schema> -d <data>`.
|
|
10
|
-
*
|
|
11
|
-
* Fallback path: if `npx --yes` cannot fetch ajv-cli (offline/sandboxed
|
|
12
|
-
* environment), fall back to a structural parse check: confirm each schema is
|
|
13
|
-
* valid Draft-07 JSON (has $schema, type) and each data file is parseable JSON.
|
|
14
|
-
* The fallback exits 0 on structural validity; CI will run the real ajv pass.
|
|
15
|
-
*
|
|
16
|
-
* Usage:
|
|
17
|
-
* node --experimental-strip-types scripts/validate-schemas.ts
|
|
18
|
-
* node --experimental-strip-types scripts/validate-schemas.ts --no-npx
|
|
19
|
-
*
|
|
20
|
-
* Exit codes:
|
|
21
|
-
* 0 — all present (schema, data) pairs pass
|
|
22
|
-
* 1 — one or more pairs failed validation or a present schema is invalid
|
|
23
|
-
*
|
|
24
|
-
* Converted from scripts/validate-schemas.cjs in Plan 20-00. Behavior is
|
|
25
|
-
* verbatim; only typing + generated-type-import were added.
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
import { spawnSync } from 'node:child_process';
|
|
29
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
30
|
-
import { resolve, join, dirname } from 'node:path';
|
|
31
|
-
|
|
32
|
-
// Generated types from reference/schemas/generated.d.ts — importing any one
|
|
33
|
-
// of them satisfies Plan 20-00's requirement that every Tier-1 TS file
|
|
34
|
-
// consumes the codegen graph. We re-export so downstream callers can also
|
|
35
|
-
// pin to these types for static validation.
|
|
36
|
-
import type {
|
|
37
|
-
ConfigSchema,
|
|
38
|
-
PluginSchema,
|
|
39
|
-
MarketplaceSchema,
|
|
40
|
-
HooksSchema,
|
|
41
|
-
IntelSchema,
|
|
42
|
-
AuthoritySnapshotSchema,
|
|
43
|
-
EventsSchema,
|
|
44
|
-
McpGddStateToolsSchema,
|
|
45
|
-
BudgetSchema,
|
|
46
|
-
RateLimitsSchema,
|
|
47
|
-
IterationBudgetSchema,
|
|
48
|
-
} from '../reference/schemas/generated.js';
|
|
49
|
-
|
|
50
|
-
export type {
|
|
51
|
-
ConfigSchema,
|
|
52
|
-
PluginSchema,
|
|
53
|
-
MarketplaceSchema,
|
|
54
|
-
HooksSchema,
|
|
55
|
-
IntelSchema,
|
|
56
|
-
AuthoritySnapshotSchema,
|
|
57
|
-
EventsSchema,
|
|
58
|
-
McpGddStateToolsSchema,
|
|
59
|
-
BudgetSchema,
|
|
60
|
-
RateLimitsSchema,
|
|
61
|
-
IterationBudgetSchema,
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Discover the repo root by walking up from cwd to find our package.json.
|
|
66
|
-
* Kept identical to the approach in tests/helpers.ts so both modules behave
|
|
67
|
-
* the same when invoked from worktrees, subdirectories, or CI.
|
|
68
|
-
*/
|
|
69
|
-
function findRepoRoot(): string {
|
|
70
|
-
let dir: string = process.cwd();
|
|
71
|
-
for (let i = 0; i < 10; i++) {
|
|
72
|
-
try {
|
|
73
|
-
const pkgPath: string = join(dir, 'package.json');
|
|
74
|
-
const pkg: { name?: string } = JSON.parse(readFileSync(pkgPath, 'utf8')) as { name?: string };
|
|
75
|
-
if (pkg.name === '@hegemonart/get-design-done') return dir;
|
|
76
|
-
} catch {
|
|
77
|
-
// not this level
|
|
78
|
-
}
|
|
79
|
-
const parent: string = dirname(dir);
|
|
80
|
-
if (parent === dir) break;
|
|
81
|
-
dir = parent;
|
|
82
|
-
}
|
|
83
|
-
return resolve(process.cwd());
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const REPO_ROOT: string = findRepoRoot();
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* (schema, data) pairs. `data` is relative to repo root. `required=true`
|
|
90
|
-
* means the data file MUST exist in the repo; `required=false` means the
|
|
91
|
-
* data file is generated at runtime (gitignored) and only the schema itself
|
|
92
|
-
* is compiled.
|
|
93
|
-
*/
|
|
94
|
-
interface Pair {
|
|
95
|
-
name: string;
|
|
96
|
-
schema: string;
|
|
97
|
-
data: string | null;
|
|
98
|
-
required: boolean;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export const PAIRS: readonly Pair[] = [
|
|
102
|
-
{
|
|
103
|
-
name: 'plugin',
|
|
104
|
-
schema: 'reference/schemas/plugin.schema.json',
|
|
105
|
-
data: '.claude-plugin/plugin.json',
|
|
106
|
-
required: true,
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
name: 'marketplace',
|
|
110
|
-
schema: 'reference/schemas/marketplace.schema.json',
|
|
111
|
-
data: '.claude-plugin/marketplace.json',
|
|
112
|
-
required: true,
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
name: 'hooks',
|
|
116
|
-
schema: 'reference/schemas/hooks.schema.json',
|
|
117
|
-
data: 'hooks/hooks.json',
|
|
118
|
-
required: true,
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
name: 'config',
|
|
122
|
-
schema: 'reference/schemas/config.schema.json',
|
|
123
|
-
data: '.design/config.json',
|
|
124
|
-
required: false,
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
name: 'intel',
|
|
128
|
-
schema: 'reference/schemas/intel.schema.json',
|
|
129
|
-
// intel files are runtime-only (gitignored). Only schema-compile it.
|
|
130
|
-
data: null,
|
|
131
|
-
required: false,
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
name: 'authority-snapshot',
|
|
135
|
-
schema: 'reference/schemas/authority-snapshot.schema.json',
|
|
136
|
-
// .design/authority-snapshot.json is runtime-only (gitignored via .design/).
|
|
137
|
-
// Only schema-compile it.
|
|
138
|
-
data: null,
|
|
139
|
-
required: false,
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
name: 'events',
|
|
143
|
-
schema: 'reference/schemas/events.schema.json',
|
|
144
|
-
// .design/telemetry/events.jsonl is runtime-only (gitignored). It is
|
|
145
|
-
// JSONL (one JSON object per line) rather than a single JSON document,
|
|
146
|
-
// so we cannot point ajv-cli at it as a `data` subject — each *line*
|
|
147
|
-
// is the schema subject, not the file. Schema-compile only here;
|
|
148
|
-
// per-line structural discipline is enforced by the EventWriter at
|
|
149
|
-
// runtime (Plan 20-06). See scripts/lib/event-stream/writer.ts.
|
|
150
|
-
data: null,
|
|
151
|
-
required: false,
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
name: 'mcp-gdd-state-tools',
|
|
155
|
-
schema: 'reference/schemas/mcp-gdd-state-tools.schema.json',
|
|
156
|
-
// The combined tool manifest is a codegen artifact — individual per-tool
|
|
157
|
-
// schemas live under scripts/mcp-servers/gdd-state/schemas/ and are
|
|
158
|
-
// consumed directly by the server. Schema-compile only here so the
|
|
159
|
-
// Draft-07 declaration stays valid as tools evolve. See Plan 20-05.
|
|
160
|
-
data: null,
|
|
161
|
-
required: false,
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
name: 'budget',
|
|
165
|
-
schema: 'reference/schemas/budget.schema.json',
|
|
166
|
-
// .design/budget.json is runtime-only (created by scripts/bootstrap.sh
|
|
167
|
-
// on first session if absent, per D-12). It's gitignored under .design/
|
|
168
|
-
// so the data file isn't tracked — schema-compile only here. The
|
|
169
|
-
// budget-enforcer.ts hook consumes BudgetSchema from generated.d.ts at
|
|
170
|
-
// type-check time regardless of file presence. See Plan 20-13.
|
|
171
|
-
data: null,
|
|
172
|
-
required: false,
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
name: 'rate-limits',
|
|
176
|
-
schema: 'reference/schemas/rate-limits.schema.json',
|
|
177
|
-
// .design/rate-limits/<provider>.json is runtime-only, written by
|
|
178
|
-
// scripts/lib/rate-guard.cjs as a side-effect of every
|
|
179
|
-
// ingestHeaders() call. One file per provider — they're all the same
|
|
180
|
-
// shape — so no single subject data path exists. Schema-compile
|
|
181
|
-
// only here; per-file structural discipline is enforced at write
|
|
182
|
-
// time by rate-guard. See Plan 20-14.
|
|
183
|
-
data: null,
|
|
184
|
-
required: false,
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
name: 'iteration-budget',
|
|
188
|
-
schema: 'reference/schemas/iteration-budget.schema.json',
|
|
189
|
-
// .design/iteration-budget.json is runtime-only, written by
|
|
190
|
-
// scripts/lib/iteration-budget.cjs. Schema-compile only here.
|
|
191
|
-
// See Plan 20-14.
|
|
192
|
-
data: null,
|
|
193
|
-
required: false,
|
|
194
|
-
},
|
|
195
|
-
];
|
|
196
|
-
|
|
197
|
-
const USE_NPX: boolean = !process.argv.includes('--no-npx');
|
|
198
|
-
|
|
199
|
-
/** Shape of an ajv-cli invocation result. */
|
|
200
|
-
interface AjvResult {
|
|
201
|
-
ok: boolean;
|
|
202
|
-
stdout: string;
|
|
203
|
-
stderr: string;
|
|
204
|
-
status: number | null;
|
|
205
|
-
fetchFailed: boolean;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Try running ajv-cli via npx. Returns { ok, stdout, stderr, status, fetchFailed }.
|
|
210
|
-
* fetchFailed=true if npx couldn't fetch the package (offline); caller should fall back.
|
|
211
|
-
*
|
|
212
|
-
* We pass `-c ajv-formats` so schemas declaring `format: "date-time"` (etc.) are
|
|
213
|
-
* validated against the standard JSON Schema formats plugin rather than being
|
|
214
|
-
* rejected as unknown formats under ajv's strict mode.
|
|
215
|
-
*/
|
|
216
|
-
function runAjv(args: readonly string[]): AjvResult {
|
|
217
|
-
const injected: string[] = [...args, '-c', 'ajv-formats'];
|
|
218
|
-
// Windows ships `npx.cmd` (batch shim); Node's spawnSync cannot invoke .cmd
|
|
219
|
-
// files without `shell: true`. On POSIX `shell: true` is a no-op here since
|
|
220
|
-
// the argv is a fixed whitelist (no user-controlled substitution). This
|
|
221
|
-
// fixes the pre-existing Windows-local failure while preserving CI
|
|
222
|
-
// behavior on Linux runners.
|
|
223
|
-
const result = spawnSync(
|
|
224
|
-
'npx',
|
|
225
|
-
['--yes', '-p', 'ajv-cli@5', '-p', 'ajv-formats@3', 'ajv', ...injected],
|
|
226
|
-
{ encoding: 'utf8', cwd: REPO_ROOT, shell: process.platform === 'win32' },
|
|
227
|
-
);
|
|
228
|
-
const stdout: string = result.stdout ?? '';
|
|
229
|
-
const stderr: string = result.stderr ?? '';
|
|
230
|
-
const combined: string = stdout + stderr;
|
|
231
|
-
// fetchFailed heuristic now also catches the "npx binary not on PATH /
|
|
232
|
-
// spawn ENOENT" case so offline or missing-npx sandboxes fall back cleanly.
|
|
233
|
-
const spawnFailed: boolean =
|
|
234
|
-
result.error !== undefined &&
|
|
235
|
-
((result.error as NodeJS.ErrnoException).code === 'ENOENT' ||
|
|
236
|
-
(result.error as NodeJS.ErrnoException).code === 'ENOTDIR');
|
|
237
|
-
const fetchFailed: boolean =
|
|
238
|
-
spawnFailed ||
|
|
239
|
-
(result.status !== 0 &&
|
|
240
|
-
/(ENOTFOUND|ECONNREFUSED|ETIMEDOUT|getaddrinfo|network|unable to resolve|unable to fetch|offline|E404|registry\.npmjs\.org)/i.test(
|
|
241
|
-
combined,
|
|
242
|
-
));
|
|
243
|
-
return {
|
|
244
|
-
ok: result.status === 0,
|
|
245
|
-
stdout,
|
|
246
|
-
stderr,
|
|
247
|
-
status: result.status,
|
|
248
|
-
fetchFailed,
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/** Outcome of a structural (fallback) schema/data parse check. */
|
|
253
|
-
interface StructuralResult {
|
|
254
|
-
ok: boolean;
|
|
255
|
-
reason?: string;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Fallback: confirm schema file is a valid Draft-07 JSON Schema (has $schema
|
|
260
|
-
* pointing at draft-07). Confirm data file (if present) parses as JSON.
|
|
261
|
-
*/
|
|
262
|
-
function structuralCheck(schemaAbs: string, dataAbs: string | null): StructuralResult {
|
|
263
|
-
let schema: { $schema?: string };
|
|
264
|
-
try {
|
|
265
|
-
schema = JSON.parse(readFileSync(schemaAbs, 'utf8')) as { $schema?: string };
|
|
266
|
-
} catch (e) {
|
|
267
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
268
|
-
return { ok: false, reason: `schema not parseable JSON: ${msg}` };
|
|
269
|
-
}
|
|
270
|
-
if (!schema.$schema || !/draft-07/.test(schema.$schema)) {
|
|
271
|
-
return { ok: false, reason: `schema missing Draft-07 $schema declaration` };
|
|
272
|
-
}
|
|
273
|
-
if (dataAbs) {
|
|
274
|
-
try {
|
|
275
|
-
JSON.parse(readFileSync(dataAbs, 'utf8'));
|
|
276
|
-
} catch (e) {
|
|
277
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
278
|
-
return { ok: false, reason: `data file not parseable JSON: ${msg}` };
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
return { ok: true };
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/** Per-pair validation result. */
|
|
285
|
-
interface PairResult {
|
|
286
|
-
name: string;
|
|
287
|
-
ok: boolean;
|
|
288
|
-
via: string;
|
|
289
|
-
note?: string;
|
|
290
|
-
reason?: string;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
function main(): void {
|
|
294
|
-
const results: PairResult[] = [];
|
|
295
|
-
let fallbackReason: string | null = null;
|
|
296
|
-
|
|
297
|
-
for (const pair of PAIRS) {
|
|
298
|
-
const schemaAbs: string = join(REPO_ROOT, pair.schema);
|
|
299
|
-
const dataAbs: string | null = pair.data ? join(REPO_ROOT, pair.data) : null;
|
|
300
|
-
|
|
301
|
-
if (!existsSync(schemaAbs)) {
|
|
302
|
-
results.push({
|
|
303
|
-
name: pair.name,
|
|
304
|
-
ok: false,
|
|
305
|
-
via: 'missing-schema',
|
|
306
|
-
reason: `schema not found at ${pair.schema}`,
|
|
307
|
-
});
|
|
308
|
-
continue;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const dataPresent: boolean = Boolean(dataAbs) && existsSync(dataAbs as string);
|
|
312
|
-
|
|
313
|
-
if (!dataPresent && pair.required) {
|
|
314
|
-
results.push({
|
|
315
|
-
name: pair.name,
|
|
316
|
-
ok: false,
|
|
317
|
-
via: 'missing-data',
|
|
318
|
-
reason: `required data file missing at ${pair.data}`,
|
|
319
|
-
});
|
|
320
|
-
continue;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (!dataPresent) {
|
|
324
|
-
// Schema-only: compile to confirm the schema is structurally valid.
|
|
325
|
-
if (USE_NPX && !fallbackReason) {
|
|
326
|
-
const r = runAjv(['compile', '-s', schemaAbs]);
|
|
327
|
-
if (r.ok) {
|
|
328
|
-
results.push({
|
|
329
|
-
name: pair.name,
|
|
330
|
-
ok: true,
|
|
331
|
-
via: 'ajv-compile',
|
|
332
|
-
note: 'data not present in repo tree — schema compiled only',
|
|
333
|
-
});
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
336
|
-
if (r.fetchFailed) {
|
|
337
|
-
fallbackReason = `npx fetch failed: ${r.stderr.split('\n')[0] || 'unknown'}`;
|
|
338
|
-
} else {
|
|
339
|
-
results.push({
|
|
340
|
-
name: pair.name,
|
|
341
|
-
ok: false,
|
|
342
|
-
via: 'ajv-compile',
|
|
343
|
-
reason: r.stderr || r.stdout || `exit ${r.status ?? 'null'}`,
|
|
344
|
-
});
|
|
345
|
-
continue;
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
const s = structuralCheck(schemaAbs, null);
|
|
349
|
-
const reason: string = s.ok
|
|
350
|
-
? 'schema is valid Draft-07 (data not in repo tree)'
|
|
351
|
-
: s.reason ?? 'unknown failure';
|
|
352
|
-
results.push({ name: pair.name, ok: s.ok, via: 'structural-compile', reason });
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Both schema and data present — full validation.
|
|
357
|
-
if (USE_NPX && !fallbackReason) {
|
|
358
|
-
const r = runAjv(['validate', '-s', schemaAbs, '-d', dataAbs as string]);
|
|
359
|
-
if (r.ok) {
|
|
360
|
-
results.push({ name: pair.name, ok: true, via: 'ajv-validate' });
|
|
361
|
-
continue;
|
|
362
|
-
}
|
|
363
|
-
if (r.fetchFailed) {
|
|
364
|
-
fallbackReason = `npx fetch failed: ${r.stderr.split('\n')[0] || 'unknown'}`;
|
|
365
|
-
} else {
|
|
366
|
-
results.push({
|
|
367
|
-
name: pair.name,
|
|
368
|
-
ok: false,
|
|
369
|
-
via: 'ajv-validate',
|
|
370
|
-
reason: r.stderr || r.stdout || `exit ${r.status ?? 'null'}`,
|
|
371
|
-
});
|
|
372
|
-
continue;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
const s = structuralCheck(schemaAbs, dataAbs);
|
|
376
|
-
const reason: string = s.ok
|
|
377
|
-
? 'schema is valid Draft-07 and data parses as JSON'
|
|
378
|
-
: s.reason ?? 'unknown failure';
|
|
379
|
-
results.push({ name: pair.name, ok: s.ok, via: 'structural-validate', reason });
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// Report.
|
|
383
|
-
console.log('validate-schemas: results');
|
|
384
|
-
for (const r of results) {
|
|
385
|
-
const status: string = r.ok ? 'OK' : 'FAIL';
|
|
386
|
-
const note: string = r.note ? ` (${r.note})` : '';
|
|
387
|
-
const reason: string = r.reason ? ` — ${r.reason}` : '';
|
|
388
|
-
console.log(` [${status}] ${r.name} via ${r.via}${note}${reason}`);
|
|
389
|
-
}
|
|
390
|
-
if (fallbackReason) {
|
|
391
|
-
console.log(
|
|
392
|
-
`\nnote: ajv-cli via npx unavailable (${fallbackReason}); fell back to structural checks. CI will run the authoritative ajv pass.`,
|
|
393
|
-
);
|
|
394
|
-
}
|
|
395
|
-
const failed: PairResult[] = results.filter((r) => !r.ok);
|
|
396
|
-
console.log(`summary: ${results.length} pair(s) checked, ${failed.length} failure(s)`);
|
|
397
|
-
|
|
398
|
-
process.exit(failed.length === 0 ? 0 : 1);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
main();
|
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
/**
|
|
4
|
-
* scripts/validate-skill-length.cjs — Phase 28.5 skill-authoring-contract validator.
|
|
5
|
-
*
|
|
6
|
-
* Walks skills/, validates per D-01/D-02/D-09/D-11:
|
|
7
|
-
* - Frontmatter required: name, description
|
|
8
|
-
* - description length: 20 <= len <= 1024 chars (always strict)
|
|
9
|
-
* - description format (--strict-description / STRICT_DESCRIPTION=1):
|
|
10
|
-
* regex `\. Use when .+\.$` (advisory by default per D-02)
|
|
11
|
-
* - SKILL.md lines: >=250 block, >=100 warn (D-01)
|
|
12
|
-
* - disable-model-invocation: only on D-09 whitelist
|
|
13
|
-
*
|
|
14
|
-
* Exit codes:
|
|
15
|
-
* 0 clean (no warnings, no blockers)
|
|
16
|
-
* 1 warnings only (>=100 lines but <250 on at least one skill, no other failures)
|
|
17
|
-
* 2 blockers present (>=250 lines, frontmatter missing/invalid, description out of range, etc.)
|
|
18
|
-
*
|
|
19
|
-
* CLI flags:
|
|
20
|
-
* --quiet Suppress per-skill output, print summary only.
|
|
21
|
-
* --strict-description Enforce `<what>. Use when <triggers>.` regex on description.
|
|
22
|
-
* --json Emit machine-readable JSON instead of human-readable text.
|
|
23
|
-
* --help, -h Print this usage message.
|
|
24
|
-
*
|
|
25
|
-
* Env:
|
|
26
|
-
* STRICT_DESCRIPTION=1 Equivalent to --strict-description.
|
|
27
|
-
* SKILLS_DIR=<path> Override skills directory (default: ./skills relative to cwd).
|
|
28
|
-
*
|
|
29
|
-
* See reference/skill-authoring-contract.md for the full contract.
|
|
30
|
-
*
|
|
31
|
-
* Line counting: counts every line in the file via split on /\r?\n/ and drops a single
|
|
32
|
-
* trailing empty entry — matches `wc -l` semantics. We count ALL lines (including blanks
|
|
33
|
-
* and comments) because that is what consumes agent context. This mirrors the same
|
|
34
|
-
* counting convention used by tests/agent-size-budget.test.cjs.
|
|
35
|
-
*
|
|
36
|
-
* Validator does NOT enforce skill renames (D-05/D-07) — frontmatter.name is checked for
|
|
37
|
-
* presence only, never compared against a canonical-name list. Whitelist key for
|
|
38
|
-
* disable-model-invocation uses the skill folder name (skills/<dir>/SKILL.md), not the
|
|
39
|
-
* frontmatter.name field.
|
|
40
|
-
*/
|
|
41
|
-
|
|
42
|
-
const fs = require('fs');
|
|
43
|
-
const path = require('path');
|
|
44
|
-
|
|
45
|
-
const WARN_LINES = 100;
|
|
46
|
-
const BLOCK_LINES = 250;
|
|
47
|
-
const DESC_MIN = 20;
|
|
48
|
-
const DESC_MAX = 1024;
|
|
49
|
-
// Strict-mode regex: trailing ". Use when <something>." sentence.
|
|
50
|
-
// Multiline + non-greedy to tolerate descriptions split across YAML lines.
|
|
51
|
-
const STRICT_RE = /\. Use when .+\.\s*$/m;
|
|
52
|
-
|
|
53
|
-
// D-09 whitelist — skills permitted to set disable-model-invocation: true.
|
|
54
|
-
// Keyed on skill folder name (skills/<folder>/SKILL.md), not frontmatter.name.
|
|
55
|
-
const DISABLE_INVOCATION_WHITELIST = new Set([
|
|
56
|
-
'help', 'stats', 'note', 'add-backlog', 'todo', 'health', 'settings',
|
|
57
|
-
'next', 'pause', 'resume', 'fast', 'quick', 'pr-branch', 'ship',
|
|
58
|
-
'reapply-patches', 'list-assumptions', 'plant-seed', 'review-backlog',
|
|
59
|
-
'cache-manager', 'warm-cache', 'synthesize', 'timeline', 'start',
|
|
60
|
-
'recall', 'continue', 'update', 'undo', 'zoom-out',
|
|
61
|
-
]);
|
|
62
|
-
|
|
63
|
-
function parseArgs(argv) {
|
|
64
|
-
const flags = { quiet: false, strict: false, json: false };
|
|
65
|
-
for (const a of argv.slice(2)) {
|
|
66
|
-
if (a === '--quiet') flags.quiet = true;
|
|
67
|
-
else if (a === '--strict-description') flags.strict = true;
|
|
68
|
-
else if (a === '--json') flags.json = true;
|
|
69
|
-
else if (a === '--help' || a === '-h') { printHelp(); process.exit(0); }
|
|
70
|
-
else { process.stderr.write(`unknown flag: ${a}\n`); process.exit(2); }
|
|
71
|
-
}
|
|
72
|
-
if (process.env.STRICT_DESCRIPTION === '1') flags.strict = true;
|
|
73
|
-
return flags;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function printHelp() {
|
|
77
|
-
process.stdout.write([
|
|
78
|
-
'validate-skill-length.cjs — Phase 28.5 skill-authoring-contract validator',
|
|
79
|
-
'',
|
|
80
|
-
'Usage: node scripts/validate-skill-length.cjs [--quiet] [--strict-description] [--json]',
|
|
81
|
-
'',
|
|
82
|
-
'Flags:',
|
|
83
|
-
' --quiet Suppress per-skill output, print summary only.',
|
|
84
|
-
' --strict-description Enforce `<what>. Use when <triggers>.` regex on description.',
|
|
85
|
-
' --json Emit machine-readable JSON.',
|
|
86
|
-
' --help, -h Show this message.',
|
|
87
|
-
'',
|
|
88
|
-
'Env:',
|
|
89
|
-
' STRICT_DESCRIPTION=1 Equivalent to --strict-description.',
|
|
90
|
-
' SKILLS_DIR=<path> Override skills directory (default: ./skills).',
|
|
91
|
-
'',
|
|
92
|
-
'Exit codes: 0=clean, 1=warnings only, 2=blockers present.',
|
|
93
|
-
'',
|
|
94
|
-
'See reference/skill-authoring-contract.md for the full contract.',
|
|
95
|
-
'',
|
|
96
|
-
].join('\n'));
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function parseFrontmatter(content) {
|
|
100
|
-
if (!content.startsWith('---\n') && !content.startsWith('---\r\n')) {
|
|
101
|
-
return { ok: false, error: 'missing-frontmatter' };
|
|
102
|
-
}
|
|
103
|
-
// Find the closing `\n---` after the opening one.
|
|
104
|
-
const end = content.indexOf('\n---', 4);
|
|
105
|
-
if (end < 0) return { ok: false, error: 'unterminated-frontmatter' };
|
|
106
|
-
const block = content.slice(4, end);
|
|
107
|
-
const fields = {};
|
|
108
|
-
const lines = block.split(/\r?\n/);
|
|
109
|
-
for (let i = 0; i < lines.length; i++) {
|
|
110
|
-
const line = lines[i];
|
|
111
|
-
const m = line.match(/^([a-zA-Z][a-zA-Z0-9_-]*):\s*(.*)$/);
|
|
112
|
-
if (m) {
|
|
113
|
-
let value = m[2];
|
|
114
|
-
// Strip wrapping quotes (single or double).
|
|
115
|
-
if (value.length >= 2 &&
|
|
116
|
-
((value.startsWith('"') && value.endsWith('"')) ||
|
|
117
|
-
(value.startsWith("'") && value.endsWith("'")))) {
|
|
118
|
-
value = value.slice(1, -1);
|
|
119
|
-
}
|
|
120
|
-
// Multi-line description folding: when description: is followed by empty value,
|
|
121
|
-
// gather continuation lines until the next `key:` line.
|
|
122
|
-
if (m[1] === 'description' && value === '') {
|
|
123
|
-
const parts = [];
|
|
124
|
-
for (let j = i + 1; j < lines.length; j++) {
|
|
125
|
-
if (/^[a-zA-Z][a-zA-Z0-9_-]*:/.test(lines[j])) break;
|
|
126
|
-
parts.push(lines[j].trim());
|
|
127
|
-
}
|
|
128
|
-
value = parts.join(' ').trim();
|
|
129
|
-
}
|
|
130
|
-
fields[m[1]] = value;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return { ok: true, fields, bodyStart: end + 4 };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function countLines(content) {
|
|
137
|
-
if (!content) return 0;
|
|
138
|
-
const lines = content.split(/\r?\n/);
|
|
139
|
-
// wc -l semantics: drop a single trailing empty entry (the newline-terminated final line).
|
|
140
|
-
if (lines[lines.length - 1] === '') lines.pop();
|
|
141
|
-
return lines.length;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function validateSkill(skillName, skillPath, flags) {
|
|
145
|
-
const result = {
|
|
146
|
-
name: skillName,
|
|
147
|
-
path: skillPath,
|
|
148
|
-
lines: 0,
|
|
149
|
-
descriptionLength: 0,
|
|
150
|
-
hasRequiredFields: false,
|
|
151
|
-
level: 'clean',
|
|
152
|
-
errors: [],
|
|
153
|
-
warnings: [],
|
|
154
|
-
reasons: [],
|
|
155
|
-
};
|
|
156
|
-
let content;
|
|
157
|
-
try {
|
|
158
|
-
content = fs.readFileSync(skillPath, 'utf8');
|
|
159
|
-
} catch (e) {
|
|
160
|
-
result.errors.push({ code: 'read-failed', message: e.message });
|
|
161
|
-
result.level = 'block';
|
|
162
|
-
result.reasons.push(`read-failed: ${e.message}`);
|
|
163
|
-
return result;
|
|
164
|
-
}
|
|
165
|
-
result.lines = countLines(content);
|
|
166
|
-
|
|
167
|
-
const fm = parseFrontmatter(content);
|
|
168
|
-
if (!fm.ok) {
|
|
169
|
-
result.errors.push({ code: 'frontmatter', message: fm.error });
|
|
170
|
-
result.reasons.push(`frontmatter: ${fm.error}`);
|
|
171
|
-
result.level = 'block';
|
|
172
|
-
return result;
|
|
173
|
-
}
|
|
174
|
-
const hasName = Boolean(fm.fields.name);
|
|
175
|
-
const hasDesc = Boolean(fm.fields.description);
|
|
176
|
-
result.hasRequiredFields = hasName && hasDesc;
|
|
177
|
-
if (!hasName) {
|
|
178
|
-
result.errors.push({ code: 'missing-name', message: 'frontmatter.name required' });
|
|
179
|
-
result.reasons.push('missing-name');
|
|
180
|
-
}
|
|
181
|
-
if (!hasDesc) {
|
|
182
|
-
result.errors.push({ code: 'missing-description', message: 'frontmatter.description required' });
|
|
183
|
-
result.reasons.push('missing-description');
|
|
184
|
-
} else {
|
|
185
|
-
const d = fm.fields.description;
|
|
186
|
-
result.descriptionLength = d.length;
|
|
187
|
-
if (d.length < DESC_MIN) {
|
|
188
|
-
result.errors.push({ code: 'description-too-short', message: `${d.length} chars; require >=${DESC_MIN}` });
|
|
189
|
-
result.reasons.push(`description-too-short: ${d.length}`);
|
|
190
|
-
}
|
|
191
|
-
if (d.length > DESC_MAX) {
|
|
192
|
-
result.errors.push({ code: 'description-too-long', message: `${d.length} chars; require <=${DESC_MAX}` });
|
|
193
|
-
result.reasons.push(`description-too-long: ${d.length}`);
|
|
194
|
-
}
|
|
195
|
-
if (flags.strict && !STRICT_RE.test(d)) {
|
|
196
|
-
result.errors.push({ code: 'description-format', message: 'strict mode: missing ". Use when <triggers>." suffix' });
|
|
197
|
-
result.reasons.push('description-format');
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
const dmi = fm.fields['disable-model-invocation'];
|
|
201
|
-
if (dmi !== undefined) {
|
|
202
|
-
if (dmi !== 'true' && dmi !== 'false') {
|
|
203
|
-
// Tolerate quoted "true"/"false" — parseFrontmatter strips quotes.
|
|
204
|
-
// Any other shape (e.g. raw "True", "yes") is a block.
|
|
205
|
-
result.errors.push({ code: 'disable-model-invocation-shape', message: `disable-model-invocation must be the boolean true|false, got "${dmi}"` });
|
|
206
|
-
result.reasons.push(`disable-model-invocation-shape: ${dmi}`);
|
|
207
|
-
} else if (dmi === 'true' && !DISABLE_INVOCATION_WHITELIST.has(skillName)) {
|
|
208
|
-
result.errors.push({ code: 'disable-model-invocation', message: `${skillName} not on D-09 whitelist` });
|
|
209
|
-
result.reasons.push('disable-model-invocation-not-whitelisted');
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
if (result.lines >= BLOCK_LINES) {
|
|
213
|
-
result.errors.push({ code: 'block-lines', message: `${result.lines} lines; block threshold ${BLOCK_LINES}` });
|
|
214
|
-
result.reasons.push(`block-lines: ${result.lines}`);
|
|
215
|
-
} else if (result.lines >= WARN_LINES) {
|
|
216
|
-
result.warnings.push({ code: 'warn-lines', message: `${result.lines} lines; warn threshold ${WARN_LINES}` });
|
|
217
|
-
result.reasons.push(`warn-lines: ${result.lines}`);
|
|
218
|
-
}
|
|
219
|
-
if (result.errors.length > 0) result.level = 'block';
|
|
220
|
-
else if (result.warnings.length > 0) result.level = 'warn';
|
|
221
|
-
else result.level = 'clean';
|
|
222
|
-
return result;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function walkSkills(skillsDir) {
|
|
226
|
-
if (!fs.existsSync(skillsDir)) return [];
|
|
227
|
-
const out = [];
|
|
228
|
-
for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
229
|
-
if (!entry.isDirectory()) continue;
|
|
230
|
-
const skillFile = path.join(skillsDir, entry.name, 'SKILL.md');
|
|
231
|
-
if (fs.existsSync(skillFile)) out.push({ name: entry.name, path: skillFile });
|
|
232
|
-
}
|
|
233
|
-
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function main() {
|
|
237
|
-
const flags = parseArgs(process.argv);
|
|
238
|
-
const root = process.cwd();
|
|
239
|
-
const skillsDir = process.env.SKILLS_DIR || path.join(root, 'skills');
|
|
240
|
-
const skills = walkSkills(skillsDir);
|
|
241
|
-
const results = skills.map(s => validateSkill(s.name, s.path, flags));
|
|
242
|
-
|
|
243
|
-
const summary = {
|
|
244
|
-
total: results.length,
|
|
245
|
-
clean: results.filter(r => r.level === 'clean').length,
|
|
246
|
-
warnings: results.filter(r => r.level === 'warn').length,
|
|
247
|
-
blockers: results.filter(r => r.level === 'block').length,
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
if (flags.json) {
|
|
251
|
-
process.stdout.write(JSON.stringify({ summary, skills: results }, null, 2) + '\n');
|
|
252
|
-
} else {
|
|
253
|
-
if (!flags.quiet) {
|
|
254
|
-
for (const r of results) {
|
|
255
|
-
if (r.level === 'clean') continue;
|
|
256
|
-
process.stdout.write(`${r.name} (${r.lines} lines):\n`);
|
|
257
|
-
for (const e of r.errors) process.stdout.write(` BLOCK ${e.code}: ${e.message}\n`);
|
|
258
|
-
for (const w of r.warnings) process.stdout.write(` WARN ${w.code}: ${w.message}\n`);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
process.stdout.write(`\nSummary: ${summary.total} skills | ${summary.clean} clean | ${summary.warnings} warn | ${summary.blockers} block\n`);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (summary.blockers > 0) process.exit(2);
|
|
265
|
-
if (summary.warnings > 0) process.exit(1);
|
|
266
|
-
process.exit(0);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
if (require.main === module) main();
|
|
270
|
-
|
|
271
|
-
module.exports = {
|
|
272
|
-
parseFrontmatter,
|
|
273
|
-
countLines,
|
|
274
|
-
validateSkill,
|
|
275
|
-
walkSkills,
|
|
276
|
-
parseArgs,
|
|
277
|
-
DISABLE_INVOCATION_WHITELIST,
|
|
278
|
-
WARN_LINES,
|
|
279
|
-
BLOCK_LINES,
|
|
280
|
-
DESC_MIN,
|
|
281
|
-
DESC_MAX,
|
|
282
|
-
STRICT_RE,
|
|
283
|
-
};
|