@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,409 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* validate-frontmatter.ts — CI-friendly frontmatter validator for agents/*.md.
|
|
4
|
-
*
|
|
5
|
-
* Enforces the Phase 7 agent frontmatter hygiene contract. Exits 0 on
|
|
6
|
-
* success, 1 on any violation. One finding per stdout line.
|
|
7
|
-
*
|
|
8
|
-
* Converted from scripts/validate-frontmatter.cjs in Plan 20-00 (Tier-1).
|
|
9
|
-
* Behavior preserved verbatim; strict types added for the frontmatter shape.
|
|
10
|
-
*
|
|
11
|
-
* Usage:
|
|
12
|
-
* node --experimental-strip-types scripts/validate-frontmatter.ts [paths...]
|
|
13
|
-
* # default path is `agents/` when none given.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { existsSync, statSync, readdirSync } from 'node:fs';
|
|
17
|
-
import { join, basename, dirname, resolve } from 'node:path';
|
|
18
|
-
import { createRequire } from 'node:module';
|
|
19
|
-
|
|
20
|
-
import { readFrontmatter } from '../tests/helpers.ts';
|
|
21
|
-
|
|
22
|
-
// ── delegate_to capability matrix loader (Plan 27-06) ──────────────────────
|
|
23
|
-
//
|
|
24
|
-
// The `delegate_to: <peer>-<role> | none` field is validated against the
|
|
25
|
-
// capability matrix exported by scripts/lib/peer-cli/registry.cjs (Plan
|
|
26
|
-
// 27-05). Loading the .cjs from a .ts module under the strip-types loader
|
|
27
|
-
// requires createRequire — and we anchor it to the repo root so the
|
|
28
|
-
// validator survives being invoked from any cwd.
|
|
29
|
-
//
|
|
30
|
-
// Loading is lazy + defensive: if the registry module isn't on disk yet
|
|
31
|
-
// (e.g. during a fresh clone before Plan 27-05 lands, or in a partial
|
|
32
|
-
// checkout), we fall back to an inline literal mirror of the locked D-05
|
|
33
|
-
// capability matrix so this validator never crashes a CI run on a
|
|
34
|
-
// missing dependency. The literal mirror MUST stay in sync with
|
|
35
|
-
// registry.cjs's CAPABILITY_MATRIX — tests assert equivalence.
|
|
36
|
-
function _findRepoRootFromHere(): string {
|
|
37
|
-
// process.argv[1] is the validator script path under strip-types; walk
|
|
38
|
-
// up from its directory looking for package.json. Fall back to cwd.
|
|
39
|
-
const start: string = (() => {
|
|
40
|
-
const argv1 = process.argv[1];
|
|
41
|
-
if (typeof argv1 === 'string' && argv1.length > 0) return dirname(argv1);
|
|
42
|
-
return process.cwd();
|
|
43
|
-
})();
|
|
44
|
-
let dir = resolve(start);
|
|
45
|
-
for (let i = 0; i < 8; i++) {
|
|
46
|
-
if (existsSync(join(dir, 'package.json'))) return dir;
|
|
47
|
-
const parent = dirname(dir);
|
|
48
|
-
if (parent === dir) break;
|
|
49
|
-
dir = parent;
|
|
50
|
-
}
|
|
51
|
-
return process.cwd();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** Locked D-05 capability matrix mirror — fallback when registry.cjs unloadable. */
|
|
55
|
-
const _DELEGATE_MATRIX_FALLBACK: Readonly<Record<string, readonly string[]>> = Object.freeze({
|
|
56
|
-
codex: Object.freeze(['execute']),
|
|
57
|
-
copilot: Object.freeze(['review', 'research']),
|
|
58
|
-
cursor: Object.freeze(['debug', 'plan']),
|
|
59
|
-
gemini: Object.freeze(['research', 'exploration']),
|
|
60
|
-
qwen: Object.freeze(['write']),
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
let _delegateMatrixCache: Readonly<Record<string, readonly string[]>> | null = null;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Return the live capability matrix as a `peer -> roles[]` map. Cached
|
|
67
|
-
* after first call. Uses registry.cjs as the source of truth; falls back
|
|
68
|
-
* to the inline mirror only if the registry module fails to load.
|
|
69
|
-
*/
|
|
70
|
-
export function loadDelegateMatrix(): Readonly<Record<string, readonly string[]>> {
|
|
71
|
-
if (_delegateMatrixCache !== null) return _delegateMatrixCache;
|
|
72
|
-
try {
|
|
73
|
-
const root = _findRepoRootFromHere();
|
|
74
|
-
const req = createRequire(join(root, 'package.json'));
|
|
75
|
-
const reg = req(resolve(root, 'scripts/lib/peer-cli/registry.cjs')) as {
|
|
76
|
-
CAPABILITY_MATRIX?: Record<string, { roles: readonly string[] }>;
|
|
77
|
-
};
|
|
78
|
-
if (reg && typeof reg.CAPABILITY_MATRIX === 'object' && reg.CAPABILITY_MATRIX !== null) {
|
|
79
|
-
const out: Record<string, readonly string[]> = {};
|
|
80
|
-
for (const [peer, cap] of Object.entries(reg.CAPABILITY_MATRIX)) {
|
|
81
|
-
if (cap && Array.isArray(cap.roles)) {
|
|
82
|
-
out[peer] = Object.freeze([...cap.roles]);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
_delegateMatrixCache = Object.freeze(out);
|
|
86
|
-
return _delegateMatrixCache;
|
|
87
|
-
}
|
|
88
|
-
} catch {
|
|
89
|
-
// fall through to fallback
|
|
90
|
-
}
|
|
91
|
-
_delegateMatrixCache = _DELEGATE_MATRIX_FALLBACK;
|
|
92
|
-
return _delegateMatrixCache;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Build the flat set of valid `<peer>-<role>` IDs from the capability
|
|
97
|
-
* matrix. e.g. `gemini-research`, `codex-execute`, etc. The literal
|
|
98
|
-
* string `"none"` is the explicit opt-out and is accepted separately.
|
|
99
|
-
*/
|
|
100
|
-
export function validDelegateIds(): readonly string[] {
|
|
101
|
-
const matrix = loadDelegateMatrix();
|
|
102
|
-
const out: string[] = [];
|
|
103
|
-
for (const [peer, roles] of Object.entries(matrix)) {
|
|
104
|
-
for (const role of roles) {
|
|
105
|
-
out.push(`${peer}-${role}`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return Object.freeze(out.sort());
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Importing a type from generated.d.ts satisfies the Plan 20-00 rule that
|
|
112
|
-
// every Tier-1 TS file participates in the codegen graph. We don't use
|
|
113
|
-
// IntelSchema at runtime; the re-export keeps it visible for static checks.
|
|
114
|
-
import type { IntelSchema } from '../reference/schemas/generated.js';
|
|
115
|
-
export type { IntelSchema };
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Strict shape of the agent-frontmatter subset this validator enforces.
|
|
119
|
-
* Matches REQUIRED_FIELDS below. `readFrontmatter` returns a permissive
|
|
120
|
-
* `Record<string, string | boolean | string[]>` — we narrow per-field as
|
|
121
|
-
* needed rather than asserting the whole object at once.
|
|
122
|
-
*/
|
|
123
|
-
export interface AgentFrontmatter {
|
|
124
|
-
name: string;
|
|
125
|
-
description: string;
|
|
126
|
-
tools: string;
|
|
127
|
-
color: string;
|
|
128
|
-
'parallel-safe': boolean | string;
|
|
129
|
-
'typical-duration-seconds': string | number;
|
|
130
|
-
'reads-only': boolean | string;
|
|
131
|
-
writes: string | string[];
|
|
132
|
-
'default-tier'?: 'haiku' | 'sonnet' | 'opus';
|
|
133
|
-
'reasoning-class'?: 'high' | 'medium' | 'low';
|
|
134
|
-
'size_budget'?: 'S' | 'M' | 'L' | 'XL';
|
|
135
|
-
/**
|
|
136
|
-
* Phase 27 (Plan 27-06) — peer-CLI delegation hint.
|
|
137
|
-
*
|
|
138
|
-
* Optional. Default unset = use local Anthropic call. Setting
|
|
139
|
-
* `delegate_to: gemini-research` tells session-runner "try delegate
|
|
140
|
-
* first, fall back to local on peer-absent / peer-error". Setting
|
|
141
|
-
* `delegate_to: none` explicitly opts out (e.g. security-sensitive
|
|
142
|
-
* agents). See agents/README.md "Peer-CLI delegation (delegate_to)"
|
|
143
|
-
* for the additive-superset rationale (CONTEXT D-06).
|
|
144
|
-
*
|
|
145
|
-
* Valid values are `<peer>-<role>` IDs that the peer-CLI registry
|
|
146
|
-
* capability matrix knows (e.g. `gemini-research`, `codex-execute`,
|
|
147
|
-
* `cursor-debug`, `cursor-plan`, `copilot-review`, `copilot-research`,
|
|
148
|
-
* `qwen-write`) plus the literal string `"none"`.
|
|
149
|
-
*/
|
|
150
|
-
'delegate_to'?: string;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const REQUIRED_FIELDS: readonly (keyof AgentFrontmatter)[] = [
|
|
154
|
-
'name',
|
|
155
|
-
'description',
|
|
156
|
-
'tools',
|
|
157
|
-
'color',
|
|
158
|
-
'parallel-safe',
|
|
159
|
-
'typical-duration-seconds',
|
|
160
|
-
'reads-only',
|
|
161
|
-
'writes',
|
|
162
|
-
];
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Phase 26 (Plan 26-08) — runtime-neutral `reasoning-class` alias for
|
|
166
|
-
* `default-tier`. Equivalence table is locked in CONTEXT D-10 / D-11:
|
|
167
|
-
*
|
|
168
|
-
* high <-> opus
|
|
169
|
-
* medium <-> sonnet
|
|
170
|
-
* low <-> haiku
|
|
171
|
-
*
|
|
172
|
-
* The alias is OPTIONAL (no per-agent retrofit lands in v1.26 — see
|
|
173
|
-
* agents/README.md "Runtime-neutral reasoning class"). When both fields
|
|
174
|
-
* appear together they MUST satisfy the equivalence; mismatched dual
|
|
175
|
-
* annotations are a validation error.
|
|
176
|
-
*/
|
|
177
|
-
export type DefaultTier = 'haiku' | 'sonnet' | 'opus';
|
|
178
|
-
export type ReasoningClass = 'high' | 'medium' | 'low';
|
|
179
|
-
|
|
180
|
-
export const REASONING_CLASS_VALUES: readonly ReasoningClass[] = [
|
|
181
|
-
'high',
|
|
182
|
-
'medium',
|
|
183
|
-
'low',
|
|
184
|
-
];
|
|
185
|
-
|
|
186
|
-
export const DEFAULT_TIER_VALUES: readonly DefaultTier[] = [
|
|
187
|
-
'opus',
|
|
188
|
-
'sonnet',
|
|
189
|
-
'haiku',
|
|
190
|
-
];
|
|
191
|
-
|
|
192
|
-
/** Equivalence map: reasoning-class -> default-tier. */
|
|
193
|
-
export const CLASS_TO_TIER: Readonly<Record<ReasoningClass, DefaultTier>> = {
|
|
194
|
-
high: 'opus',
|
|
195
|
-
medium: 'sonnet',
|
|
196
|
-
low: 'haiku',
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
/** Equivalence map: default-tier -> reasoning-class. */
|
|
200
|
-
export const TIER_TO_CLASS: Readonly<Record<DefaultTier, ReasoningClass>> = {
|
|
201
|
-
opus: 'high',
|
|
202
|
-
sonnet: 'medium',
|
|
203
|
-
haiku: 'low',
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
/** Type guard for a valid `reasoning-class` value. */
|
|
207
|
-
export function isReasoningClass(v: unknown): v is ReasoningClass {
|
|
208
|
-
return typeof v === 'string' && REASONING_CLASS_VALUES.includes(v as ReasoningClass);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/** Type guard for a valid `default-tier` value. */
|
|
212
|
-
export function isDefaultTier(v: unknown): v is DefaultTier {
|
|
213
|
-
return typeof v === 'string' && DEFAULT_TIER_VALUES.includes(v as DefaultTier);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Validate the optional `reasoning-class` field and its equivalence with
|
|
218
|
-
* `default-tier` when both are present. Returns an array of violation
|
|
219
|
-
* messages; an empty array means the agent passes the Plan 26-08 rules.
|
|
220
|
-
*
|
|
221
|
-
* Rules (Plan 26-08, CONTEXT D-11):
|
|
222
|
-
* 1. `reasoning-class` is OPTIONAL. Absence is fine.
|
|
223
|
-
* 2. If present, it MUST be one of `high|medium|low`.
|
|
224
|
-
* 3. If both `default-tier` and `reasoning-class` are present, the values
|
|
225
|
-
* MUST satisfy the equivalence table (high+opus, medium+sonnet,
|
|
226
|
-
* low+haiku). Mismatch is a validation error.
|
|
227
|
-
*
|
|
228
|
-
* Existing agents that carry only `default-tier` (the v1.26 baseline state
|
|
229
|
-
* for all 26 shipped agents) are unaffected — this helper returns an empty
|
|
230
|
-
* array for them.
|
|
231
|
-
*
|
|
232
|
-
* The `agentName` argument is used in error messages to surface which agent
|
|
233
|
-
* is misconfigured when the validator runs against the full roster.
|
|
234
|
-
*/
|
|
235
|
-
export function validateReasoningClass(
|
|
236
|
-
fm: Record<string, unknown>,
|
|
237
|
-
agentName: string,
|
|
238
|
-
): string[] {
|
|
239
|
-
const violations: string[] = [];
|
|
240
|
-
const hasClass = 'reasoning-class' in fm && !isMissing(fm['reasoning-class']);
|
|
241
|
-
const hasTier = 'default-tier' in fm && !isMissing(fm['default-tier']);
|
|
242
|
-
|
|
243
|
-
if (!hasClass) {
|
|
244
|
-
// Field absent — allowed. `default-tier` is the v1.26 source of truth and
|
|
245
|
-
// is enforced by separate Phase 10.1 contracts (not this validator).
|
|
246
|
-
return violations;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const rawClass = fm['reasoning-class'];
|
|
250
|
-
if (!isReasoningClass(rawClass)) {
|
|
251
|
-
violations.push(
|
|
252
|
-
`reasoning-class: invalid value "${String(rawClass)}" for agent "${agentName}" — must be one of ${REASONING_CLASS_VALUES.join('|')}`,
|
|
253
|
-
);
|
|
254
|
-
return violations;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (hasTier) {
|
|
258
|
-
const rawTier = fm['default-tier'];
|
|
259
|
-
if (!isDefaultTier(rawTier)) {
|
|
260
|
-
// default-tier shape is enforced elsewhere; we still surface a clear
|
|
261
|
-
// message so co-validation is debuggable in one pass.
|
|
262
|
-
violations.push(
|
|
263
|
-
`default-tier: invalid value "${String(rawTier)}" for agent "${agentName}" — must be one of ${DEFAULT_TIER_VALUES.join('|')}`,
|
|
264
|
-
);
|
|
265
|
-
return violations;
|
|
266
|
-
}
|
|
267
|
-
const expectedTier = CLASS_TO_TIER[rawClass];
|
|
268
|
-
if (rawTier !== expectedTier) {
|
|
269
|
-
violations.push(
|
|
270
|
-
`reasoning-class/default-tier: mismatch for agent "${agentName}" — reasoning-class="${rawClass}" expects default-tier="${expectedTier}", but got default-tier="${rawTier}". Equivalence table: high<->opus, medium<->sonnet, low<->haiku.`,
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return violations;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Validate the optional Phase 27 (Plan 27-06) `delegate_to` field. Returns
|
|
280
|
-
* an array of violation messages; empty means the agent passes.
|
|
281
|
-
*
|
|
282
|
-
* Rules (CONTEXT D-06):
|
|
283
|
-
* 1. `delegate_to` is OPTIONAL. Absence = use local Anthropic call.
|
|
284
|
-
* 2. If present, value MUST be a string.
|
|
285
|
-
* 3. The literal value `"none"` is accepted as the explicit opt-out.
|
|
286
|
-
* 4. Any other value MUST match a `<peer>-<role>` ID drawn from the
|
|
287
|
-
* peer-CLI capability matrix (Plan 27-05's registry.cjs is the
|
|
288
|
-
* source of truth; loadDelegateMatrix() resolves it lazily with
|
|
289
|
-
* a literal fallback if the registry is unloadable).
|
|
290
|
-
*
|
|
291
|
-
* The 26 v1.26 baseline agents do not carry this field; this helper
|
|
292
|
-
* returns `[]` for them. The validator runs trivially clean on them.
|
|
293
|
-
*/
|
|
294
|
-
export function validateDelegateTo(
|
|
295
|
-
fm: Record<string, unknown>,
|
|
296
|
-
agentName: string,
|
|
297
|
-
): string[] {
|
|
298
|
-
const violations: string[] = [];
|
|
299
|
-
const has = 'delegate_to' in fm && !isMissing(fm['delegate_to']);
|
|
300
|
-
if (!has) return violations;
|
|
301
|
-
|
|
302
|
-
const raw = fm['delegate_to'];
|
|
303
|
-
if (typeof raw !== 'string') {
|
|
304
|
-
violations.push(
|
|
305
|
-
`delegate_to: invalid value "${String(raw)}" for agent "${agentName}" — must be a string ("none" or "<peer>-<role>" e.g. "gemini-research")`,
|
|
306
|
-
);
|
|
307
|
-
return violations;
|
|
308
|
-
}
|
|
309
|
-
if (raw === 'none') return violations; // explicit opt-out
|
|
310
|
-
|
|
311
|
-
const valid = validDelegateIds();
|
|
312
|
-
if (!valid.includes(raw)) {
|
|
313
|
-
violations.push(
|
|
314
|
-
`delegate_to: invalid value "${raw}" for agent "${agentName}" — must be "none" or one of: ${valid.join(', ')} (peer-CLI capability matrix; see scripts/lib/peer-cli/registry.cjs)`,
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
return violations;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function walkMd(dir: string): string[] {
|
|
321
|
-
const out: string[] = [];
|
|
322
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
323
|
-
const full: string = join(dir, entry.name);
|
|
324
|
-
if (entry.isDirectory()) out.push(...walkMd(full));
|
|
325
|
-
else if (entry.isFile() && entry.name.endsWith('.md')) out.push(full);
|
|
326
|
-
}
|
|
327
|
-
return out;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/**
|
|
331
|
-
* Return true when the frontmatter value is "missing" per the original
|
|
332
|
-
* .cjs contract: undefined / null / empty string. Preserves the semantics
|
|
333
|
-
* exactly so the CI gate fires on the same inputs.
|
|
334
|
-
*/
|
|
335
|
-
function isMissing(v: unknown): boolean {
|
|
336
|
-
if (v === undefined || v === null) return true;
|
|
337
|
-
if (typeof v === 'string' && v === '') return true;
|
|
338
|
-
return false;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
function main(): void {
|
|
342
|
-
const args: string[] = process.argv.slice(2).filter((a) => !a.startsWith('--'));
|
|
343
|
-
const targets: string[] = args.length ? args : ['agents/'];
|
|
344
|
-
const files: string[] = [];
|
|
345
|
-
for (const t of targets) {
|
|
346
|
-
if (!existsSync(t)) {
|
|
347
|
-
console.error(`${t}: path does not exist`);
|
|
348
|
-
process.exit(1);
|
|
349
|
-
}
|
|
350
|
-
const stat = statSync(t);
|
|
351
|
-
if (stat.isDirectory()) files.push(...walkMd(t));
|
|
352
|
-
else files.push(t);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
let violations = 0;
|
|
356
|
-
for (const f of files) {
|
|
357
|
-
const fm = readFrontmatter(f);
|
|
358
|
-
if (Object.keys(fm).length === 0) {
|
|
359
|
-
// README.md under agents/ may have no frontmatter — skip
|
|
360
|
-
if (basename(f).toLowerCase() === 'readme.md') continue;
|
|
361
|
-
console.log(`${f}:frontmatter: missing`);
|
|
362
|
-
violations++;
|
|
363
|
-
continue;
|
|
364
|
-
}
|
|
365
|
-
for (const field of REQUIRED_FIELDS) {
|
|
366
|
-
if (!(field in fm) || isMissing((fm as Record<string, unknown>)[field])) {
|
|
367
|
-
console.log(`${f}:${field}: missing`);
|
|
368
|
-
violations++;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// Plan 26-08 — runtime-neutral reasoning-class alias validation.
|
|
373
|
-
const agentName: string =
|
|
374
|
-
typeof fm.name === 'string' && fm.name.length > 0
|
|
375
|
-
? fm.name
|
|
376
|
-
: basename(f).replace(/\.md$/, '');
|
|
377
|
-
const classViolations = validateReasoningClass(
|
|
378
|
-
fm as Record<string, unknown>,
|
|
379
|
-
agentName,
|
|
380
|
-
);
|
|
381
|
-
for (const msg of classViolations) {
|
|
382
|
-
console.log(`${f}:${msg}`);
|
|
383
|
-
violations++;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Plan 27-06 — peer-CLI delegate_to validation (additive optional field).
|
|
387
|
-
const delegateViolations = validateDelegateTo(
|
|
388
|
-
fm as Record<string, unknown>,
|
|
389
|
-
agentName,
|
|
390
|
-
);
|
|
391
|
-
for (const msg of delegateViolations) {
|
|
392
|
-
console.log(`${f}:${msg}`);
|
|
393
|
-
violations++;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
console.log(`summary: ${files.length} file(s) checked, ${violations} violation(s)`);
|
|
398
|
-
process.exit(violations === 0 ? 0 : 1);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// Only run as a CLI when invoked directly (Plan 26-08: tests import the
|
|
402
|
-
// helpers above without triggering process.exit). Node's strip-types ESM
|
|
403
|
-
// loader sets `process.argv[1]` to the resolved entry path; a substring
|
|
404
|
-
// match against this filename catches both direct execution and the
|
|
405
|
-
// `node --experimental-strip-types` wrapper used by `npm run validate:frontmatter`.
|
|
406
|
-
const entry: string = process.argv[1] ?? '';
|
|
407
|
-
if (entry.endsWith('validate-frontmatter.ts') || entry.endsWith('validate-frontmatter.js')) {
|
|
408
|
-
main();
|
|
409
|
-
}
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/validate-incubator-scope.cjs — Plan 29-05
|
|
3
|
-
//
|
|
4
|
-
// Phase 29 D-05: scope guard for incubator-draft promotion.
|
|
5
|
-
//
|
|
6
|
-
// Purpose
|
|
7
|
-
// Enforce that a drafted incubator artifact can only resolve to one of:
|
|
8
|
-
// * `agents/<slug>.md` (Phase 28.5 agent files)
|
|
9
|
-
// * `skills/<slug>/SKILL.md` (Phase 28.5 skill files)
|
|
10
|
-
// Any other path (script, hook, runtime, transport, root-escape, absolute
|
|
11
|
-
// path outside the repo, traversal segment) is rejected with a non-zero
|
|
12
|
-
// exit and an informative error message.
|
|
13
|
-
//
|
|
14
|
-
// This script is invoked BEFORE any file write inside
|
|
15
|
-
// `scripts/lib/apply-reflections/incubator-proposals.cjs#applyAccept`, and
|
|
16
|
-
// is the second non-bypassable line of defense after the floor enforced by
|
|
17
|
-
// `scripts/lib/incubator-author.cjs#safeWritePath` at draft-time.
|
|
18
|
-
//
|
|
19
|
-
// Non-bypassable (D-05)
|
|
20
|
-
// No flag, env var, or argument disables the check. Promotion targets that
|
|
21
|
-
// fail the regex check throw — period. There is no opt-out flag, no
|
|
22
|
-
// environment override, and the CLI offers no escape hatch. (The scan in
|
|
23
|
-
// tests/apply-reflections-incubator.test.cjs grep-asserts the absence of
|
|
24
|
-
// bypass tokens in this file's source, so even adding such an option in
|
|
25
|
-
// future would break the build.)
|
|
26
|
-
//
|
|
27
|
-
// API
|
|
28
|
-
// validateScope(targetPath, { repoRoot } = {})
|
|
29
|
-
// → { ok: true } // accepted
|
|
30
|
-
// → throws Error(...) // rejected; message names offending path + allowed patterns
|
|
31
|
-
//
|
|
32
|
-
// CLI
|
|
33
|
-
// node scripts/validate-incubator-scope.cjs <path>
|
|
34
|
-
// exit 0 + `[scope-guard] ok: <relPath>` on success
|
|
35
|
-
// exit 1 + descriptive stderr on failure
|
|
36
|
-
//
|
|
37
|
-
// Style: zero deps beyond node:fs + node:path (matches scripts/lib/incubator-author.cjs).
|
|
38
|
-
|
|
39
|
-
'use strict';
|
|
40
|
-
|
|
41
|
-
const path = require('node:path');
|
|
42
|
-
|
|
43
|
-
// Allowed target patterns — slug rules match the Phase 28.5 frontmatter slug
|
|
44
|
-
// regex (lowercase, digits, hyphens; must start with [a-z0-9]).
|
|
45
|
-
const SLUG_RE_FRAGMENT = '[a-z0-9][a-z0-9-]*';
|
|
46
|
-
const AGENT_RE = new RegExp(`^agents/${SLUG_RE_FRAGMENT}\\.md$`);
|
|
47
|
-
const SKILL_RE = new RegExp(`^skills/${SLUG_RE_FRAGMENT}/SKILL\\.md$`);
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Validate that a target path is in scope for incubator promotion.
|
|
51
|
-
*
|
|
52
|
-
* Algorithm:
|
|
53
|
-
* 1. Resolve to absolute path under repoRoot.
|
|
54
|
-
* 2. Reject if the resolved path escapes repoRoot (path traversal or
|
|
55
|
-
* absolute path pointing outside the repository).
|
|
56
|
-
* 3. Compute repo-relative path with forward-slash normalization.
|
|
57
|
-
* 4. Reject if the relative path doesn't match exactly one of the two
|
|
58
|
-
* allowed patterns.
|
|
59
|
-
*
|
|
60
|
-
* @param {string} targetPath - file path to validate; relative paths are
|
|
61
|
-
* resolved against repoRoot.
|
|
62
|
-
* @param {{repoRoot?: string}} [opts] - configuration. repoRoot defaults to
|
|
63
|
-
* process.cwd().
|
|
64
|
-
* @returns {{ok: true}} on success.
|
|
65
|
-
* @throws {Error} on any rejection. Message includes the offending path and
|
|
66
|
-
* the allowed patterns.
|
|
67
|
-
*/
|
|
68
|
-
function validateScope(targetPath, opts) {
|
|
69
|
-
const o = opts || {};
|
|
70
|
-
const repoRoot = path.resolve(o.repoRoot || process.cwd());
|
|
71
|
-
|
|
72
|
-
if (typeof targetPath !== 'string' || !targetPath.length) {
|
|
73
|
-
throw new Error(
|
|
74
|
-
`[scope-guard] invalid input: targetPath must be a non-empty string. ` +
|
|
75
|
-
`Allowed: ${AGENT_RE.source} or ${SKILL_RE.source}`,
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Resolve relative to repoRoot. Absolute paths bypass repoRoot prefixing;
|
|
80
|
-
// that's fine — the prefix check below catches them anyway.
|
|
81
|
-
const resolved = path.resolve(repoRoot, targetPath);
|
|
82
|
-
|
|
83
|
-
// Step 1: confirm resolved path is inside repoRoot. We compare with a
|
|
84
|
-
// trailing separator to avoid `repoRoot-evil/...` slipping past a startsWith
|
|
85
|
-
// check.
|
|
86
|
-
const rootWithSep = repoRoot + path.sep;
|
|
87
|
-
if (!(resolved === repoRoot || resolved.startsWith(rootWithSep))) {
|
|
88
|
-
throw new Error(
|
|
89
|
-
`[scope-guard] path escapes repository: ${targetPath} → ${resolved} ` +
|
|
90
|
-
`(outside ${repoRoot}). Allowed: agents/<slug>.md or skills/<slug>/SKILL.md`,
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Step 2: compute repo-relative path and normalize separators to '/'
|
|
95
|
-
// (Windows uses '\\' natively).
|
|
96
|
-
const rel = path.relative(repoRoot, resolved).replace(/\\/g, '/');
|
|
97
|
-
|
|
98
|
-
// Step 3: match exactly one of the allowed shapes.
|
|
99
|
-
if (AGENT_RE.test(rel) || SKILL_RE.test(rel)) {
|
|
100
|
-
return { ok: true };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
throw new Error(
|
|
104
|
-
`[scope-guard] path not in allowed scope: ${rel} ` +
|
|
105
|
-
`(input: ${targetPath}). Allowed patterns: ` +
|
|
106
|
-
`agents/<slug>.md (regex ${AGENT_RE.source}) ` +
|
|
107
|
-
`or skills/<slug>/SKILL.md (regex ${SKILL_RE.source}). ` +
|
|
108
|
-
`Note: scope guard is non-bypassable per Phase 29 D-05.`,
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
module.exports = { validateScope };
|
|
113
|
-
|
|
114
|
-
// -------------------------------------------------------------------
|
|
115
|
-
// CLI entry
|
|
116
|
-
// -------------------------------------------------------------------
|
|
117
|
-
|
|
118
|
-
if (require.main === module) {
|
|
119
|
-
const input = process.argv[2];
|
|
120
|
-
if (!input) {
|
|
121
|
-
console.error('[scope-guard] usage: node scripts/validate-incubator-scope.cjs <path>');
|
|
122
|
-
process.exit(1);
|
|
123
|
-
}
|
|
124
|
-
try {
|
|
125
|
-
validateScope(input);
|
|
126
|
-
const rel = path.relative(process.cwd(), path.resolve(process.cwd(), input)).replace(/\\/g, '/');
|
|
127
|
-
console.log(`[scope-guard] ok: ${rel}`);
|
|
128
|
-
process.exit(0);
|
|
129
|
-
} catch (err) {
|
|
130
|
-
console.error(err.message);
|
|
131
|
-
process.exit(1);
|
|
132
|
-
}
|
|
133
|
-
}
|