@event4u/agent-config 11.0.0 → 12.0.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 +1 -1
- package/CHANGELOG.md +71 -249
- package/dist/agent-src/commands/agent-handoff.md +0 -1
- package/dist/agent-src/commands/agent-status.md +0 -1
- package/dist/agent-src/commands/agents/audit.md +0 -1
- package/dist/agent-src/commands/agents/init.md +0 -1
- package/dist/agent-src/commands/agents/optimize.md +0 -1
- package/dist/agent-src/commands/agents/user/accept.md +0 -1
- package/dist/agent-src/commands/agents/user/delete.md +0 -1
- package/dist/agent-src/commands/agents/user/init.md +0 -1
- package/dist/agent-src/commands/agents/user/review.md +0 -1
- package/dist/agent-src/commands/agents/user/show.md +0 -1
- package/dist/agent-src/commands/agents/user/update.md +0 -1
- package/dist/agent-src/commands/agents/user.md +0 -1
- package/dist/agent-src/commands/agents.md +0 -1
- package/dist/agent-src/commands/analytics/prune.md +0 -1
- package/dist/agent-src/commands/analytics/show.md +0 -1
- package/dist/agent-src/commands/analytics.md +0 -1
- package/dist/agent-src/commands/analyze/conformance.md +0 -1
- package/dist/agent-src/commands/analyze/decision.md +0 -1
- package/dist/agent-src/commands/analyze/inbox.md +0 -1
- package/dist/agent-src/commands/analyze/incident.md +0 -1
- package/dist/agent-src/commands/analyze/near-miss.md +0 -1
- package/dist/agent-src/commands/analyze/postmortem.md +0 -1
- package/dist/agent-src/commands/analyze/premortem.md +0 -1
- package/dist/agent-src/commands/analyze/reference-repo.md +0 -1
- package/dist/agent-src/commands/analyze.md +0 -1
- package/dist/agent-src/commands/brand/identity.md +0 -1
- package/dist/agent-src/commands/brand/review.md +0 -1
- package/dist/agent-src/commands/brand/strategy.md +0 -1
- package/dist/agent-src/commands/brand/tokens.md +0 -1
- package/dist/agent-src/commands/brand/voice.md +0 -1
- package/dist/agent-src/commands/brand.md +0 -1
- package/dist/agent-src/commands/bug/fix.md +0 -1
- package/dist/agent-src/commands/bug/investigate.md +0 -1
- package/dist/agent-src/commands/bug.md +0 -1
- package/dist/agent-src/commands/challenge-me/vision.md +0 -1
- package/dist/agent-src/commands/challenge-me/with-docs.md +0 -1
- package/dist/agent-src/commands/challenge-me.md +0 -1
- package/dist/agent-src/commands/chat-history/import.md +0 -1
- package/dist/agent-src/commands/chat-history.md +0 -1
- package/dist/agent-src/commands/check-current-md.md +0 -1
- package/dist/agent-src/commands/commit/in-chunks.md +0 -1
- package/dist/agent-src/commands/commit.md +0 -1
- package/dist/agent-src/commands/condense.md +0 -1
- package/dist/agent-src/commands/context/create.md +0 -1
- package/dist/agent-src/commands/context/refactor.md +0 -1
- package/dist/agent-src/commands/context.md +0 -1
- package/dist/agent-src/commands/contribution-precheck.md +0 -1
- package/dist/agent-src/commands/cost/profile.md +0 -1
- package/dist/agent-src/commands/cost/report.md +0 -1
- package/dist/agent-src/commands/cost.md +0 -1
- package/dist/agent-src/commands/council/analysis.md +0 -1
- package/dist/agent-src/commands/council/debate.md +0 -1
- package/dist/agent-src/commands/council/default.md +0 -1
- package/dist/agent-src/commands/council/design.md +0 -1
- package/dist/agent-src/commands/council/optimize.md +0 -1
- package/dist/agent-src/commands/council/pr.md +0 -1
- package/dist/agent-src/commands/council.md +0 -1
- package/dist/agent-src/commands/design-system/capture.md +0 -1
- package/dist/agent-src/commands/design-system/generate.md +0 -1
- package/dist/agent-src/commands/design-system/import.md +0 -1
- package/dist/agent-src/commands/design-system.md +0 -1
- package/dist/agent-src/commands/estimate-ticket.md +0 -1
- package/dist/agent-src/commands/explain-run.md +0 -1
- package/dist/agent-src/commands/feature/dev.md +0 -1
- package/dist/agent-src/commands/feature/explore.md +0 -1
- package/dist/agent-src/commands/feature/plan.md +0 -1
- package/dist/agent-src/commands/feature/refactor.md +0 -1
- package/dist/agent-src/commands/feature/roadmap.md +0 -1
- package/dist/agent-src/commands/feature.md +0 -1
- package/dist/agent-src/commands/fix/ci.md +0 -1
- package/dist/agent-src/commands/fix/comments.md +0 -1
- package/dist/agent-src/commands/fix/portability.md +0 -1
- package/dist/agent-src/commands/fix/pr-comments-loop.md +0 -1
- package/dist/agent-src/commands/fix/pr-comments.md +0 -1
- package/dist/agent-src/commands/fix/quality.md +0 -1
- package/dist/agent-src/commands/fix/refs.md +0 -1
- package/dist/agent-src/commands/fix/route.md +0 -1
- package/dist/agent-src/commands/fix/seeder.md +0 -1
- package/dist/agent-src/commands/fix.md +0 -1
- package/dist/agent-src/commands/ghostwriter/delete.md +0 -1
- package/dist/agent-src/commands/ghostwriter/fetch.md +0 -1
- package/dist/agent-src/commands/ghostwriter/list.md +0 -1
- package/dist/agent-src/commands/ghostwriter/show.md +0 -1
- package/dist/agent-src/commands/ghostwriter/write.md +0 -1
- package/dist/agent-src/commands/ghostwriter.md +0 -1
- package/dist/agent-src/commands/grill-me.md +0 -1
- package/dist/agent-src/commands/humanize.md +0 -1
- package/dist/agent-src/commands/image/analyse.md +0 -1
- package/dist/agent-src/commands/image/create.md +0 -1
- package/dist/agent-src/commands/image/verify.md +0 -1
- package/dist/agent-src/commands/image.md +0 -1
- package/dist/agent-src/commands/implement-ticket.md +0 -1
- package/dist/agent-src/commands/jira-ticket.md +0 -1
- package/dist/agent-src/commands/judge/on-diff.md +0 -1
- package/dist/agent-src/commands/judge/solo.md +0 -1
- package/dist/agent-src/commands/judge/steps.md +0 -1
- package/dist/agent-src/commands/judge.md +0 -1
- package/dist/agent-src/commands/knowledge/cross-repo.md +0 -1
- package/dist/agent-src/commands/knowledge/forget.md +0 -1
- package/dist/agent-src/commands/knowledge/ingest.md +0 -1
- package/dist/agent-src/commands/knowledge/list.md +0 -1
- package/dist/agent-src/commands/knowledge.md +0 -1
- package/dist/agent-src/commands/memory/add.md +0 -1
- package/dist/agent-src/commands/memory/learn-low-impact.md +0 -1
- package/dist/agent-src/commands/memory/load.md +0 -1
- package/dist/agent-src/commands/memory/mine-session.md +0 -1
- package/dist/agent-src/commands/memory/promote.md +0 -1
- package/dist/agent-src/commands/memory/propose.md +0 -1
- package/dist/agent-src/commands/memory.md +0 -1
- package/dist/agent-src/commands/mission/upgrade.md +0 -1
- package/dist/agent-src/commands/mode.md +0 -1
- package/dist/agent-src/commands/module/create.md +0 -1
- package/dist/agent-src/commands/module/explore.md +0 -1
- package/dist/agent-src/commands/module.md +0 -1
- package/dist/agent-src/commands/optimize/agents-dir.md +0 -1
- package/dist/agent-src/commands/optimize/augmentignore.md +0 -1
- package/dist/agent-src/commands/optimize/deep.md +0 -1
- package/dist/agent-src/commands/optimize/project.md +0 -1
- package/dist/agent-src/commands/optimize/prompt.md +0 -1
- package/dist/agent-src/commands/optimize/rtk.md +0 -1
- package/dist/agent-src/commands/optimize/skills.md +0 -1
- package/dist/agent-src/commands/optimize.md +0 -1
- package/dist/agent-src/commands/orchestrate.md +0 -1
- package/dist/agent-src/commands/override/create.md +0 -1
- package/dist/agent-src/commands/override/manage.md +0 -1
- package/dist/agent-src/commands/override.md +0 -1
- package/dist/agent-src/commands/package/reset.md +0 -1
- package/dist/agent-src/commands/package/test.md +0 -1
- package/dist/agent-src/commands/package.md +0 -1
- package/dist/agent-src/commands/post-as/ghostwriter.md +0 -1
- package/dist/agent-src/commands/post-as/me.md +0 -1
- package/dist/agent-src/commands/post-as.md +0 -1
- package/dist/agent-src/commands/pr/create/description-only.md +0 -1
- package/dist/agent-src/commands/pr/create.md +23 -9
- package/dist/agent-src/commands/prediction-pool.md +0 -1
- package/dist/agent-src/commands/prepare-for-review.md +0 -1
- package/dist/agent-src/commands/profile/activate.md +0 -1
- package/dist/agent-src/commands/profile/deactivate.md +0 -1
- package/dist/agent-src/commands/profile/show.md +0 -1
- package/dist/agent-src/commands/profile.md +0 -1
- package/dist/agent-src/commands/project/analyze.md +0 -1
- package/dist/agent-src/commands/project/health.md +0 -1
- package/dist/agent-src/commands/project.md +0 -1
- package/dist/agent-src/commands/refine-ticket.md +0 -1
- package/dist/agent-src/commands/research/deep.md +0 -1
- package/dist/agent-src/commands/research/report.md +0 -1
- package/dist/agent-src/commands/research.md +0 -1
- package/dist/agent-src/commands/review/changes.md +0 -1
- package/dist/agent-src/commands/review/routing.md +0 -1
- package/dist/agent-src/commands/review.md +0 -1
- package/dist/agent-src/commands/roadmap/ai-council.md +0 -1
- package/dist/agent-src/commands/roadmap/create.md +0 -1
- package/dist/agent-src/commands/roadmap/materialize.md +0 -1
- package/dist/agent-src/commands/roadmap/next.md +0 -1
- package/dist/agent-src/commands/roadmap/process-full.md +0 -1
- package/dist/agent-src/commands/roadmap/process-phase.md +0 -1
- package/dist/agent-src/commands/roadmap/process-step.md +0 -1
- package/dist/agent-src/commands/roadmap.md +0 -1
- package/dist/agent-src/commands/rule-compliance-audit.md +0 -1
- package/dist/agent-src/commands/security-audit-config.md +0 -1
- package/dist/agent-src/commands/skill/preview.md +0 -1
- package/dist/agent-src/commands/skill.md +0 -1
- package/dist/agent-src/commands/skills/discover.md +0 -1
- package/dist/agent-src/commands/skills.md +0 -1
- package/dist/agent-src/commands/sync/agent-settings.md +0 -1
- package/dist/agent-src/commands/sync/gitignore/fix.md +0 -1
- package/dist/agent-src/commands/sync/gitignore.md +0 -1
- package/dist/agent-src/commands/sync.md +0 -1
- package/dist/agent-src/commands/tdd/green.md +0 -1
- package/dist/agent-src/commands/tdd/red.md +0 -1
- package/dist/agent-src/commands/tdd/refactor.md +0 -1
- package/dist/agent-src/commands/tdd.md +0 -1
- package/dist/agent-src/commands/team/adversarial.md +0 -1
- package/dist/agent-src/commands/team/delegate.md +0 -1
- package/dist/agent-src/commands/team/review.md +0 -1
- package/dist/agent-src/commands/team/status.md +0 -1
- package/dist/agent-src/commands/team-knowledge/bootstrap.md +0 -1
- package/dist/agent-src/commands/team-knowledge/consolidate.md +0 -1
- package/dist/agent-src/commands/team-knowledge.md +0 -1
- package/dist/agent-src/commands/team.md +0 -1
- package/dist/agent-src/commands/tests/create.md +0 -1
- package/dist/agent-src/commands/tests/e2e-heal.md +0 -1
- package/dist/agent-src/commands/tests/e2e-plan.md +0 -1
- package/dist/agent-src/commands/tests/execute.md +0 -1
- package/dist/agent-src/commands/tests.md +0 -1
- package/dist/agent-src/commands/threat-model.md +0 -1
- package/dist/agent-src/commands/update-form-request-messages.md +0 -1
- package/dist/agent-src/commands/upstream-contribute.md +0 -1
- package/dist/agent-src/commands/video/from-script.md +0 -1
- package/dist/agent-src/commands/video/from-song.md +0 -1
- package/dist/agent-src/commands/video/scene.md +0 -1
- package/dist/agent-src/commands/video/stitch.md +0 -1
- package/dist/agent-src/commands/video/storyboard.md +0 -1
- package/dist/agent-src/commands/video.md +0 -1
- package/dist/agent-src/commands/work.md +0 -1
- package/dist/agent-src/commands/worktree/cleanup.md +0 -1
- package/dist/agent-src/commands/worktree/create.md +0 -1
- package/dist/agent-src/commands/worktree/status.md +0 -1
- package/dist/agent-src/commands/worktree/verify.md +0 -1
- package/dist/agent-src/commands/worktree.md +0 -1
- package/dist/agent-src/skills/docx-authoring/SKILL.md +1 -0
- package/dist/agent-src/skills/git-workflow/SKILL.md +9 -1
- package/dist/agent-src/skills/pdf-tools/SKILL.md +1 -0
- package/dist/agent-src/skills/skill-writing/SKILL.md +35 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/commands/commands.candidates.test.js +1 -1
- package/dist/cli/commands/commands.candidates.test.js.map +1 -1
- package/dist/cli/commands/commands.js +9 -15
- package/dist/cli/commands/commands.js.map +1 -1
- package/dist/cli/commands/profiles.test.js +7 -7
- package/dist/cli/commands/profiles.test.js.map +1 -1
- package/dist/cli/discovery/loadManifest.js.map +1 -1
- package/dist/cli/main.js +2 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/cli-delegate/cmd_conformance.js +14 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +211 -410
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +2 -2
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +3 -3
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +3 -3
- package/dist/hooks/dispatch.js +14 -2
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +46 -5
- package/docs/MIGRATION.md +1 -0
- package/docs/contracts/settings-classes.md +41 -0
- package/package.json +1 -1
- package/src/config/agent-settings.template.yml +34 -0
- package/src/config/gate-violation-baselines.json +3 -3
- package/src/scripts/_dispatch.bash +42 -5
- package/src/scripts/audit_command_surface.ts +21 -15
- package/src/scripts/build_catalog_index.ts +1 -1
- package/src/scripts/build_discovery_manifest.ts +30 -34
- package/src/scripts/check_branch_freshness.ts +210 -61
- package/src/scripts/gen_discovery_baseline.ts +3 -3
- package/src/scripts/hook_manifest.yaml +20 -0
- package/src/scripts/hooks/subagent_ledger_hook.ts +17 -4
- package/src/scripts/lint_command_tiers.ts +29 -75
- package/src/scripts/lint_originality.ts +101 -14
- package/src/scripts/schemas/command.schema.json +4 -9
- package/src/scripts/schemas/skill.schema.json +10 -1
- package/src/scripts/skill_linter.ts +82 -2
- package/src/scripts/worktree_cleanup_check.ts +111 -7
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Lint slash-command frontmatter for the `
|
|
3
|
+
* Lint slash-command frontmatter for the `visibility:` key.
|
|
4
4
|
*
|
|
5
5
|
* Ported from the retired Python `src/scripts/lint_command_tiers.py` (ADR-200, Phase 4 /
|
|
6
|
-
* Wave 4b).
|
|
7
|
-
* ordering,
|
|
8
|
-
*
|
|
9
|
-
* condensed-projection passes).
|
|
6
|
+
* Wave 4b). Keeps the Python CLI contract's shape: same scan scope, file
|
|
7
|
+
* ordering, stdout/stderr split, and exit codes (return-code bitwise-OR
|
|
8
|
+
* accumulation across the domain + per-root + condensed-projection passes).
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
* tier
|
|
10
|
+
* The integer `tier:` alias this file was named for is GONE
|
|
11
|
+
* (road-to-tier-removal Phase 4): `visibility:` is now the sole command-surface
|
|
12
|
+
* classifier. The filename is retained because the gate id, its Taskfile entry,
|
|
13
|
+
* and the contract doc all key on it; only the checked key changed.
|
|
14
|
+
*
|
|
15
|
+
* Hard-fails CI if any command lacks a `visibility:` declaration or uses an
|
|
16
|
+
* unknown value. The valid set is locked by
|
|
13
17
|
* docs/contracts/command-surface-tiers.md.
|
|
14
18
|
*
|
|
15
19
|
* Exit codes:
|
|
16
|
-
* 0 every command declares a valid
|
|
17
|
-
* 1 one or more commands missing or using an invalid
|
|
20
|
+
* 0 every command declares a valid visibility
|
|
21
|
+
* 1 one or more commands missing or using an invalid visibility
|
|
18
22
|
*/
|
|
19
23
|
import fs from "node:fs";
|
|
20
24
|
import path from "node:path";
|
|
@@ -79,20 +83,13 @@ function COMMANDS_DIRS(): string[] {
|
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
const DOMAINS_DIR = path.join(REPO, "src", "domains");
|
|
82
|
-
// Consumer-facing projection — must also carry
|
|
83
|
-
// (which symlinks to dist/agent-src/commands/) renders the
|
|
86
|
+
// Consumer-facing projection — must also carry visibility so .augment/commands/
|
|
87
|
+
// (which symlinks to dist/agent-src/commands/) renders the surface filter.
|
|
84
88
|
const COMMANDS_DIR_CONDENSED = path.join(REPO, "dist/agent-src", "commands");
|
|
85
89
|
|
|
86
|
-
|
|
87
|
-
//
|
|
88
|
-
// back-compat integer alias. Both are validated; when both are present they
|
|
89
|
-
// MUST agree per this mapping.
|
|
90
|
+
// ADR-092 named `visibility:` the source of truth; road-to-tier-removal Phase 4
|
|
91
|
+
// removed the integer `tier:` alias, so this set is the whole vocabulary.
|
|
90
92
|
const VALID_VISIBILITIES: ReadonlySet<string> = new Set(["visible", "advanced", "internal"]);
|
|
91
|
-
const TIER_TO_VISIBILITY: Readonly<Record<string, string>> = {
|
|
92
|
-
"0": "visible",
|
|
93
|
-
"1": "advanced",
|
|
94
|
-
"2": "internal",
|
|
95
|
-
};
|
|
96
93
|
|
|
97
94
|
export function parse_field(text: string, key: string): string | null {
|
|
98
95
|
if (!text.startsWith("---\n")) {
|
|
@@ -116,15 +113,13 @@ export function parse_field(text: string, key: string): string | null {
|
|
|
116
113
|
return null;
|
|
117
114
|
}
|
|
118
115
|
|
|
119
|
-
export function parse_tier(text: string): string | null {
|
|
120
|
-
return parse_field(text, "tier");
|
|
121
|
-
}
|
|
122
|
-
|
|
123
116
|
/**
|
|
124
117
|
* Validate the visibility field (ADR-092). Returns an error string or null.
|
|
125
118
|
*
|
|
126
|
-
* Requires a present + valid visibility
|
|
127
|
-
*
|
|
119
|
+
* Requires a present + valid visibility. The tier↔visibility consistency clause
|
|
120
|
+
* is gone with the alias itself (road-to-tier-removal Phase 4) — a stray `tier:`
|
|
121
|
+
* key is now rejected by `command.schema.json` (`additionalProperties: false`),
|
|
122
|
+
* not reconciled here.
|
|
128
123
|
*/
|
|
129
124
|
export function visibility_error(text: string): string | null {
|
|
130
125
|
const vis = parse_field(text, "visibility");
|
|
@@ -134,10 +129,6 @@ export function visibility_error(text: string): string | null {
|
|
|
134
129
|
if (!VALID_VISIBILITIES.has(vis)) {
|
|
135
130
|
return `invalid visibility '${vis}'`;
|
|
136
131
|
}
|
|
137
|
-
const tier = parse_field(text, "tier");
|
|
138
|
-
if (tier !== null && tier in TIER_TO_VISIBILITY && TIER_TO_VISIBILITY[tier] !== vis) {
|
|
139
|
-
return `visibility '${vis}' disagrees with tier '${tier}' (expected '${TIER_TO_VISIBILITY[tier]}')`;
|
|
140
|
-
}
|
|
141
132
|
return null;
|
|
142
133
|
}
|
|
143
134
|
|
|
@@ -153,12 +144,6 @@ function stripChar(s: string, ch: string): string {
|
|
|
153
144
|
return s.slice(start, end);
|
|
154
145
|
}
|
|
155
146
|
|
|
156
|
-
// Mirror Python's `print(sorted(set))` list repr: ['0', '1', ...].
|
|
157
|
-
function sortedTiersRepr(): string {
|
|
158
|
-
const items = [...VALID_TIERS].sort();
|
|
159
|
-
return `[${items.map((s) => `'${s}'`).join(", ")}]`;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
147
|
// Mirror Python's `print(sorted(VALID_VISIBILITIES))` list repr.
|
|
163
148
|
function sortedVisibilitiesRepr(): string {
|
|
164
149
|
const items = [...VALID_VISIBILITIES].sort();
|
|
@@ -184,43 +169,27 @@ export function lint(commands_dir: string, quiet = false): number {
|
|
|
184
169
|
return 1;
|
|
185
170
|
}
|
|
186
171
|
|
|
187
|
-
const missing: string[] = [];
|
|
188
|
-
const invalid: Array<[string, string]> = [];
|
|
189
172
|
const vis_errors: Array<[string, string]> = [];
|
|
190
173
|
|
|
191
174
|
for (const cmd of commands) {
|
|
192
175
|
const rel = relPosix(cmd, commands_dir);
|
|
193
176
|
const text = fs.readFileSync(cmd, "utf-8");
|
|
194
|
-
const tier = parse_tier(text);
|
|
195
|
-
if (tier === null) {
|
|
196
|
-
missing.push(rel);
|
|
197
|
-
} else if (!VALID_TIERS.has(tier)) {
|
|
198
|
-
invalid.push([rel, tier]);
|
|
199
|
-
}
|
|
200
177
|
const ve = visibility_error(text);
|
|
201
178
|
if (ve !== null) {
|
|
202
179
|
vis_errors.push([rel, ve]);
|
|
203
180
|
}
|
|
204
181
|
}
|
|
205
182
|
|
|
206
|
-
if (
|
|
183
|
+
if (vis_errors.length > 0) {
|
|
207
184
|
process.stderr.write(
|
|
208
|
-
`❌ lint_command_tiers: ${
|
|
209
|
-
`${invalid.length} invalid tier, ${vis_errors.length} visibility ` +
|
|
185
|
+
`❌ lint_command_tiers: ${vis_errors.length} visibility ` +
|
|
210
186
|
`(of ${commands.length} commands)\n`,
|
|
211
187
|
);
|
|
212
|
-
for (const name of missing) {
|
|
213
|
-
process.stderr.write(` missing tier: ${name}\n`);
|
|
214
|
-
}
|
|
215
|
-
for (const [name, tier] of invalid) {
|
|
216
|
-
process.stderr.write(` invalid tier '${tier}': ${name}\n`);
|
|
217
|
-
}
|
|
218
188
|
for (const [name, err] of vis_errors) {
|
|
219
189
|
process.stderr.write(` ${err}: ${name}\n`);
|
|
220
190
|
}
|
|
221
191
|
process.stderr.write(
|
|
222
|
-
` valid
|
|
223
|
-
`valid visibility: ${sortedVisibilitiesRepr()}\n`,
|
|
192
|
+
` valid visibility: ${sortedVisibilitiesRepr()}\n`,
|
|
224
193
|
);
|
|
225
194
|
process.stderr.write(
|
|
226
195
|
" contract: docs/contracts/command-surface-tiers.md (ADR-092)\n",
|
|
@@ -231,7 +200,7 @@ export function lint(commands_dir: string, quiet = false): number {
|
|
|
231
200
|
if (!quiet) {
|
|
232
201
|
process.stdout.write(
|
|
233
202
|
`✅ lint_command_tiers: ${commands.length} commands, ` +
|
|
234
|
-
"all
|
|
203
|
+
"all visibility values valid\n",
|
|
235
204
|
);
|
|
236
205
|
}
|
|
237
206
|
return 0;
|
|
@@ -246,35 +215,20 @@ export function lint_domain_sources(quiet = false): number {
|
|
|
246
215
|
);
|
|
247
216
|
return 1;
|
|
248
217
|
}
|
|
249
|
-
const missing: string[] = [];
|
|
250
|
-
const invalid: Array<[string, string]> = [];
|
|
251
218
|
const vis_errors: Array<[string, string]> = [];
|
|
252
219
|
for (const c of commands) {
|
|
253
220
|
const rel = relPosix(c, REPO);
|
|
254
221
|
const text = fs.readFileSync(c, "utf-8");
|
|
255
|
-
const t = parse_tier(text);
|
|
256
|
-
if (t === null) {
|
|
257
|
-
missing.push(rel);
|
|
258
|
-
} else if (!VALID_TIERS.has(t)) {
|
|
259
|
-
invalid.push([rel, t]);
|
|
260
|
-
}
|
|
261
222
|
const ve = visibility_error(text);
|
|
262
223
|
if (ve !== null) {
|
|
263
224
|
vis_errors.push([rel, ve]);
|
|
264
225
|
}
|
|
265
226
|
}
|
|
266
|
-
if (
|
|
227
|
+
if (vis_errors.length > 0) {
|
|
267
228
|
process.stderr.write(
|
|
268
|
-
`❌ lint_command_tiers: ${
|
|
269
|
-
`${invalid.length} invalid tier, ${vis_errors.length} visibility ` +
|
|
229
|
+
`❌ lint_command_tiers: ${vis_errors.length} visibility ` +
|
|
270
230
|
`(of ${commands.length} domain commands)\n`,
|
|
271
231
|
);
|
|
272
|
-
for (const name of missing) {
|
|
273
|
-
process.stderr.write(` missing tier: ${name}\n`);
|
|
274
|
-
}
|
|
275
|
-
for (const [name, tier] of invalid) {
|
|
276
|
-
process.stderr.write(` invalid tier '${tier}': ${name}\n`);
|
|
277
|
-
}
|
|
278
232
|
for (const [name, err] of vis_errors) {
|
|
279
233
|
process.stderr.write(` ${err}: ${name}\n`);
|
|
280
234
|
}
|
|
@@ -283,7 +237,7 @@ export function lint_domain_sources(quiet = false): number {
|
|
|
283
237
|
if (!quiet) {
|
|
284
238
|
process.stdout.write(
|
|
285
239
|
`✅ lint_command_tiers: ${commands.length} domain commands, ` +
|
|
286
|
-
"all
|
|
240
|
+
"all visibility values valid\n",
|
|
287
241
|
);
|
|
288
242
|
}
|
|
289
243
|
return 0;
|
|
@@ -354,7 +308,7 @@ export function main(): number {
|
|
|
354
308
|
}
|
|
355
309
|
// The condensed projection is the consumer-facing tree (via the
|
|
356
310
|
// .augment/commands → dist/agent-src/commands symlink). It must also
|
|
357
|
-
// carry
|
|
311
|
+
// carry visibility so the surface stays uniform.
|
|
358
312
|
if (_isDir(COMMANDS_DIR_CONDENSED)) {
|
|
359
313
|
rc |= lint(COMMANDS_DIR_CONDENSED, QUIET);
|
|
360
314
|
}
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
*
|
|
21
21
|
* Modes:
|
|
22
22
|
* (no args) full pairwise audit → agents/reports/originality.{json,md}
|
|
23
|
+
* --base <ref> the revision the change set is measured against; defines
|
|
24
|
+
* what counts as ESTABLISHED scaffold (see below)
|
|
23
25
|
* --changed <f...> CI path: each changed file vs the whole same-class corpus
|
|
24
26
|
* AND vs the other changed files of its class
|
|
25
27
|
*
|
|
@@ -41,7 +43,7 @@
|
|
|
41
43
|
* it drifts as commands/skills are added or removed. Re-run the full audit
|
|
42
44
|
* after any large corpus change; do not treat 40 % as invariant.
|
|
43
45
|
* 2. Adversarial batch masking. In `--changed` mode the boilerplate set is
|
|
44
|
-
*
|
|
46
|
+
* derived from the ESTABLISHED corpus, so a batch of ≥ _dfFloor
|
|
45
47
|
* near-identical re-skins submitted together cannot classify its own shared
|
|
46
48
|
* shingles as boilerplate (see `_changed`). The full-audit sweep (no
|
|
47
49
|
* "changed" notion) does NOT have this guard — a batch of ≥ floor identical
|
|
@@ -51,8 +53,35 @@
|
|
|
51
53
|
* computes the PR's changed corpus files and runs `--changed` on them
|
|
52
54
|
* (empty changed set reports INCONCLUSIVE, never a pass).
|
|
53
55
|
*
|
|
56
|
+
* HOW "established" is resolved, and why it changed (2026-08-14):
|
|
57
|
+
* With `--base <ref>` the established corpus is the class corpus AS IT
|
|
58
|
+
* EXISTS AT THAT REVISION — content read from git, not from the worktree.
|
|
59
|
+
* Without it, the older approximation applies: on-disk corpus MINUS the
|
|
60
|
+
* change set.
|
|
61
|
+
*
|
|
62
|
+
* That approximation has a degeneracy the base-ref form does not. When a
|
|
63
|
+
* change set covers (nearly) the whole class — a mechanical edit such as
|
|
64
|
+
* dropping one frontmatter key from every command — "corpus minus change
|
|
65
|
+
* set" is EMPTY, no shingle clears the DF floor, nothing is scaffold, and
|
|
66
|
+
* the class's shared skeleton is scored as authored overlap. Measured on
|
|
67
|
+
* road-to-tier-removal Phase 4: the full audit reported worst 40 % / 0
|
|
68
|
+
* failures, while `--changed` over the same 200 unmodified-in-substance
|
|
69
|
+
* files reported 67.9 % and 3 failures — byte-identical on the base
|
|
70
|
+
* revision's own content, so the delta was entirely the masking collapse.
|
|
71
|
+
*
|
|
72
|
+
* The base-ref form does NOT weaken the guard, and it is worth being
|
|
73
|
+
* precise about why rather than claiming more. Both forms close the
|
|
74
|
+
* re-skin-batch axis, for different reasons: the old one excludes the
|
|
75
|
+
* change set outright, the new one relies on those files' shared shingles
|
|
76
|
+
* being absent from the base content (a NEW file contributes nothing at
|
|
77
|
+
* all; a REWRITTEN file contributes only its OLD text). Either way a
|
|
78
|
+
* batch cannot promote its own new shingles to scaffold. What changes is
|
|
79
|
+
* only the degenerate case: an established corpus that is empty because
|
|
80
|
+
* the change set swallowed it.
|
|
81
|
+
*
|
|
54
82
|
* Exit 0 clean/warn · 1 fail · 2 usage.
|
|
55
83
|
*/
|
|
84
|
+
import { spawnSync } from 'node:child_process';
|
|
56
85
|
import * as fs from 'node:fs';
|
|
57
86
|
import * as path from 'node:path';
|
|
58
87
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
@@ -176,7 +205,11 @@ function _templateShingles(spec: ClassSpec): Set<string> {
|
|
|
176
205
|
}
|
|
177
206
|
|
|
178
207
|
function _load(abs: string, cls: ArtifactClass, tmpl: Set<string>): Artifact {
|
|
179
|
-
|
|
208
|
+
return _loadText(fs.readFileSync(abs, 'utf-8'), abs, cls, tmpl);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** `_load` over supplied text — used to load a file's content at a base ref. */
|
|
212
|
+
function _loadText(text: string, abs: string, cls: ArtifactClass, tmpl: Set<string>): Artifact {
|
|
180
213
|
const raw = shingles(text, K);
|
|
181
214
|
// Subtract shared scaffold — a shingle also present in the class template
|
|
182
215
|
// is boilerplate, not authored content.
|
|
@@ -332,14 +365,41 @@ function _writeReport(byClass: Map<ArtifactClass, Artifact[]>, pairs: Pair[], di
|
|
|
332
365
|
|
|
333
366
|
// --- changed mode ------------------------------------------------------------
|
|
334
367
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
368
|
+
/**
|
|
369
|
+
* A corpus file's content at `ref`, or null when the file does not exist there.
|
|
370
|
+
*
|
|
371
|
+
* Null is the load-bearing case: a file absent at the base revision is NEW in
|
|
372
|
+
* this change set, so it contributes nothing to the scaffold definition. That is
|
|
373
|
+
* what keeps a batch of re-skinned newcomers from masking itself.
|
|
374
|
+
*
|
|
375
|
+
* A git failure is indistinguishable from "absent" at this layer, and both
|
|
376
|
+
* resolve the same conservative way — the file is excluded from scaffold, which
|
|
377
|
+
* can only make the gate stricter, never blinder.
|
|
378
|
+
*/
|
|
379
|
+
function _textAtRef(ref: string, relpath: string): string | null {
|
|
380
|
+
const r = spawnSync('git', ['show', `${ref}:${relpath}`], {
|
|
381
|
+
cwd: ROOT,
|
|
382
|
+
encoding: 'utf-8',
|
|
383
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
384
|
+
});
|
|
385
|
+
if (r.status !== 0 || typeof r.stdout !== 'string') return null;
|
|
386
|
+
return r.stdout;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function _changed(files: string[], base: string | null = null): { pairs: Pair[]; fails: number } {
|
|
390
|
+
// The boilerplate DF set is derived from the ESTABLISHED corpus only.
|
|
391
|
+
// Without this, a batch of ≥ _dfFloor near-identical re-skins submitted
|
|
392
|
+
// together would lift its OWN shared shingles over the floor, be
|
|
393
|
+
// reclassified as boilerplate, subtracted from every copy, and score 0
|
|
394
|
+
// against each other — the gate blinds itself on exactly the attack it
|
|
395
|
+
// exists to catch. "What is scaffold" must be defined by the corpus as it
|
|
396
|
+
// stands, never by the diff proposing to change it.
|
|
397
|
+
//
|
|
398
|
+
// `--base` makes "as it stands" literal: scaffold comes from the class
|
|
399
|
+
// corpus AT THAT REVISION. Without it we fall back to the older
|
|
400
|
+
// approximation (on-disk minus the change set), which is equivalent for a
|
|
401
|
+
// small change set but degenerates to an EMPTY established corpus when the
|
|
402
|
+
// change set covers the class — see the § 2 note in the module header.
|
|
343
403
|
const changedRel = new Set(files.map((f) => _rel(path.resolve(ROOT, f))));
|
|
344
404
|
|
|
345
405
|
// Build the corpus in RAW (template-subtracted, pre-DF) form per class so we
|
|
@@ -351,8 +411,18 @@ function _changed(files: string[]): { pairs: Pair[]; fails: number } {
|
|
|
351
411
|
const tmpl = _templateShingles(spec);
|
|
352
412
|
templateCache.set(spec.name, tmpl);
|
|
353
413
|
const arts = spec.files().map((f) => _load(f, spec.name, tmpl));
|
|
354
|
-
// Established corpus
|
|
355
|
-
|
|
414
|
+
// Established corpus: at `base` when given (a file absent there is NEW
|
|
415
|
+
// and contributes no scaffold), else on-disk arts minus the change set.
|
|
416
|
+
const established =
|
|
417
|
+
base === null
|
|
418
|
+
? arts.filter((a) => !changedRel.has(a.relpath))
|
|
419
|
+
: arts
|
|
420
|
+
.map((a) => {
|
|
421
|
+
const text = _textAtRef(base, a.relpath);
|
|
422
|
+
return text === null ? null : _loadText(text, path.resolve(ROOT, a.relpath), spec.name, tmpl);
|
|
423
|
+
})
|
|
424
|
+
.filter((a): a is Artifact => a !== null);
|
|
425
|
+
const boiler = _boilerplateSet(established);
|
|
356
426
|
for (const a of arts) _subtract(a, boiler);
|
|
357
427
|
byClass.set(spec.name, arts);
|
|
358
428
|
boilerByClass.set(spec.name, boiler);
|
|
@@ -404,13 +474,30 @@ export function main(argv?: string[]): number {
|
|
|
404
474
|
}
|
|
405
475
|
let quiet = false;
|
|
406
476
|
let changedFiles: string[] | null = null;
|
|
477
|
+
let base: string | null = null;
|
|
407
478
|
for (let i = 0; i < args.length; i++) {
|
|
408
479
|
const a = args[i] as string;
|
|
409
480
|
if (a === '--quiet') { quiet = true; }
|
|
481
|
+
else if (a === '--base') {
|
|
482
|
+
// `--changed` swallows the rest of argv, so `--base` must precede it.
|
|
483
|
+
const v = args[i + 1];
|
|
484
|
+
if (v === undefined || v.startsWith('-')) {
|
|
485
|
+
process.stderr.write(`--base needs a git ref\n${_usage()}`);
|
|
486
|
+
return 2;
|
|
487
|
+
}
|
|
488
|
+
base = v;
|
|
489
|
+
i += 1;
|
|
490
|
+
}
|
|
410
491
|
else if (a === '--changed') { changedFiles = args.slice(i + 1).filter((x) => !x.startsWith('-')); break; }
|
|
411
492
|
else if (a.startsWith('-')) { process.stderr.write(`unrecognized argument: ${a}\n${_usage()}`); return 2; }
|
|
412
493
|
else { process.stderr.write(`unexpected argument: ${a} (did you mean --changed ${a}?)\n${_usage()}`); return 2; }
|
|
413
494
|
}
|
|
495
|
+
if (base !== null && changedFiles === null) {
|
|
496
|
+
// Silently ignoring it would let a workflow think the base-anchored
|
|
497
|
+
// scaffold was in effect when the full audit does not use one at all.
|
|
498
|
+
process.stderr.write(`--base is only meaningful with --changed\n${_usage()}`);
|
|
499
|
+
return 2;
|
|
500
|
+
}
|
|
414
501
|
|
|
415
502
|
// Asserted for BOTH modes, and before either: `--changed` compares each
|
|
416
503
|
// candidate against the same corpus, so a dead root there reports "no
|
|
@@ -434,7 +521,7 @@ export function main(argv?: string[]): number {
|
|
|
434
521
|
}
|
|
435
522
|
|
|
436
523
|
if (changedFiles !== null) {
|
|
437
|
-
const { pairs, fails } = _changed(changedFiles);
|
|
524
|
+
const { pairs, fails } = _changed(changedFiles, base);
|
|
438
525
|
if (pairs.length > 0) {
|
|
439
526
|
const stream = fails > 0 ? process.stderr : process.stdout;
|
|
440
527
|
stream.write(`lint_originality: ${pairs.length} overlap(s) ≥ WARN ${WARN}% (${fails} ≥ FAIL ${FAIL}%):\n`);
|
|
@@ -466,7 +553,7 @@ export function main(argv?: string[]): number {
|
|
|
466
553
|
}
|
|
467
554
|
|
|
468
555
|
function _usage(): string {
|
|
469
|
-
return 'usage: lint_originality [--quiet] [--changed <file...>]\n';
|
|
556
|
+
return 'usage: lint_originality [--quiet] [--base <ref>] [--changed <file...>]\n';
|
|
470
557
|
}
|
|
471
558
|
|
|
472
559
|
function _isCliEntry(): boolean {
|
|
@@ -14,15 +14,10 @@
|
|
|
14
14
|
"maxLength": 64,
|
|
15
15
|
"$comment": "Always the path-derived hyphen slug (`commit`, `council-default`) — identical to the `.claude/skills/` directory slug generated by condense.py. Agent-Skills-spec-compliant (lowercase letters, digits, hyphens; no leading/trailing/double hyphen; <=64 chars) so strict consumers like Zed accept projected SKILL.md entries. Colon names (`council:default`) are FORBIDDEN since the 2026-06 Zed fix; Claude Code's `/cluster:sub` invocation is path-derived from `commands/<cluster>/<sub>.md` and unaffected by this field."
|
|
16
16
|
},
|
|
17
|
-
"tier": {
|
|
18
|
-
"type": "integer",
|
|
19
|
-
"enum": [0, 1, 2],
|
|
20
|
-
"description": "Command-surface tier per docs/contracts/command-surface-tiers.md. 0 = daily-driver (rendered in default `./agent-config --help`), 1 = power-user (rendered with `--tier=1`), 2 = maintenance/internal (rendered only with `--tier=all`). Default for new commands is 2 — promotion is gated by ADR criteria, never by author preference. BACK-COMPAT ALIAS since ADR-092: `visibility:` is the named source of truth (visible↔0, advanced↔1, internal↔2); `tier:` is retained as a derived integer alias and dual-emitted in the discovery manifest during the deprecation window. When both are present they MUST agree (enforced by lint_command_tiers.py)."
|
|
21
|
-
},
|
|
22
17
|
"visibility": {
|
|
23
18
|
"type": "string",
|
|
24
19
|
"enum": ["visible", "advanced", "internal"],
|
|
25
|
-
"description": "Command-surface visibility
|
|
20
|
+
"description": "Command-surface visibility per docs/contracts/command-surface-tiers.md — the sole command-surface classifier since the integer `tier:` alias was removed. `visible` = daily-driver (default `./agent-config --help`), `advanced` = power-user (`--tier=1`), `internal` = maintenance/hidden (`--tier=all` only). Read by the surface classifier (commands ls/explain), the per-pack visible-command budget audit, and the discovery-manifest builder. Default for new commands is `internal`; promotion is gated by ADR criteria. Presence and validity are enforced by lint_command_tiers.ts."
|
|
26
21
|
},
|
|
27
22
|
"description": {
|
|
28
23
|
"type": "string",
|
|
@@ -71,7 +66,7 @@
|
|
|
71
66
|
"type": "string",
|
|
72
67
|
"minLength": 1,
|
|
73
68
|
"maxLength": 200,
|
|
74
|
-
"description": "6.0.0-C Phase 2 routing metadata. One-line existence justification: the single user intent this command serves (e.g. 'Implement a ticket end-to-end from its tracker id'). REQUIRED on every visible command (
|
|
69
|
+
"description": "6.0.0-C Phase 2 routing metadata. One-line existence justification: the single user intent this command serves (e.g. 'Implement a ticket end-to-end from its tracker id'). REQUIRED on every visible command (visibility `visible`/`advanced`), enforced by scripts/lint_command_routing.py; optional on `internal` commands. The human-readable answer to 'why does this command exist?'"
|
|
75
70
|
},
|
|
76
71
|
"routes_to": {
|
|
77
72
|
"type": "array",
|
|
@@ -81,7 +76,7 @@
|
|
|
81
76
|
"type": "string",
|
|
82
77
|
"pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$"
|
|
83
78
|
},
|
|
84
|
-
"description": "6.0.0-C Phase 2 routing metadata. The data backbone the routing-eval lint checks against: the skill slug(s), cluster sub-command(s), or command(s) this command routes the intent to (e.g. ['refine-ticket', 'feature-planning'] or ['fix-ci']). Always hyphen slugs — colon identifiers forbidden since the 2026-06 Zed fix. REQUIRED on every visible command (
|
|
79
|
+
"description": "6.0.0-C Phase 2 routing metadata. The data backbone the routing-eval lint checks against: the skill slug(s), cluster sub-command(s), or command(s) this command routes the intent to (e.g. ['refine-ticket', 'feature-planning'] or ['fix-ci']). Always hyphen slugs — colon identifiers forbidden since the 2026-06 Zed fix. REQUIRED on every visible command (visibility `visible`/`advanced`), enforced by scripts/lint_command_routing.py; optional on `internal` commands."
|
|
85
80
|
},
|
|
86
81
|
"category": {
|
|
87
82
|
"type": "string",
|
|
@@ -96,7 +91,7 @@
|
|
|
96
91
|
"pattern": "^[a-z][a-z0-9-]*(:[a-z][a-z0-9-]*)?$",
|
|
97
92
|
"$comment": "Colon form stays ALLOWED here on purpose: `replaces` lists PRIOR names/aliases, and pre-2026-06 colon names (`commit:in-chunks`) are legitimate historical aliases."
|
|
98
93
|
},
|
|
99
|
-
"description": "6.0.0-C Phase 2 routing metadata. The alias / deprecation link: prior command name(s) or alias(es) this command absorbs or supersedes. Empty array `[]` when the command replaces nothing (the common case). REQUIRED-as-a-key on every visible command (
|
|
94
|
+
"description": "6.0.0-C Phase 2 routing metadata. The alias / deprecation link: prior command name(s) or alias(es) this command absorbs or supersedes. Empty array `[]` when the command replaces nothing (the common case). REQUIRED-as-a-key on every visible command (visibility `visible`/`advanced`) so the routing surface is explicit, enforced by scripts/lint_command_routing.py; optional on `internal` commands. Distinct from `superseded_by` (set on the OLD shim pointing forward); `replaces` is set on the NEW canonical command pointing back."
|
|
100
95
|
},
|
|
101
96
|
"type": {
|
|
102
97
|
"type": "string",
|
|
@@ -31,7 +31,16 @@
|
|
|
31
31
|
"compatibility": {
|
|
32
32
|
"type": "string",
|
|
33
33
|
"maxLength": 500,
|
|
34
|
-
"
|
|
34
|
+
"deprecated": true,
|
|
35
|
+
"description": "DEPRECATED (announced 2026-08-14, removal maintainer-scheduled — see docs/MIGRATION.md § Scheduled deprecations). Agent-Skills spec field (optional): free-text declaration of environment requirements — system packages, network access, product/host constraints. Superseded by `harness_compat` (the machine-readable class) plus `runtime_requires` (the probeable detail). Still accepted and still emitted so the public-spec mirror keeps working during the window; new skills declare `harness_compat` instead."
|
|
36
|
+
},
|
|
37
|
+
"harness_compat": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"enum": [
|
|
40
|
+
"consumer-installed-deps"
|
|
41
|
+
],
|
|
42
|
+
"description": "Structured replacement for the free-text `compatibility` class claim: WHAT KIND of harness constraint the skill carries, in a form `doctor`/`preflight` can branch on. Distinct from `runtime_requires`, which names WHICH bins/env/network to probe — a Python module such as `python-docx` is not a PATH binary, so it has no `bins` entry and would otherwise be invisible to any machine reader. Deliberately single-valued: the enum is derived from the only two skills that ever declared `compatibility`, and both attest exactly one class. A second value is added when a skill attests one, never pre-emptively — a speculative vocabulary is the pro-forma-field risk this schema already refuses for `runtime_requires`. `consumer-installed-deps` = the skill runs only where the consumer has itself installed named third-party libraries or engines; this package ships no runtime for it.",
|
|
43
|
+
"$comment": "road-to-skill-ecosystem-executable-payloads Phase 1, under the maintainer's 2026-08-14 resolution of blocker `compatibility-deprecation-is-a-consumer-visible-decision`: ADDITIVE — `harness_compat` lands beside `compatibility`, which is NOT removed here; its removal is approved in principle and scheduled through docs/MIGRATION.md § Scheduled deprecations, never dropped silently. Enum evidence (verbatim, the complete population of `compatibility` users): docx-authoring — \"Requires a consumer-installed OOXML library (python-docx …) … Ships zero runtime in this package.\"; pdf-tools — \"Requires consumer-installed PDF libraries: pypdf …, reportlab …; an OCR engine (e.g. tesseract) … Ships zero runtime in this package.\" Both state one class; the label generalises \"library\"/\"engine\" to \"deps\" and adds no claim neither text makes."
|
|
35
44
|
},
|
|
36
45
|
"runtime_requires": {
|
|
37
46
|
"type": "object",
|
|
@@ -254,6 +254,42 @@ const EXTERNAL_EXECUTION_HANDLERS = new Set(['shell', 'php', 'node']);
|
|
|
254
254
|
* span lines and accept a bare key whose value sits somewhere below it.
|
|
255
255
|
*/
|
|
256
256
|
const RUNTIME_REQUIRES_BLOCK_PATTERN = /^runtime_requires:[ \t]*(\{.*)?$/m;
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* The deprecated free-text `compatibility:` key and its structured successor
|
|
260
|
+
* `harness_compat:`, both top-level. Anchored at column 0 so a `compatibility:`
|
|
261
|
+
* nested inside another block (or the word inside a quoted description) is not
|
|
262
|
+
* read as the frontmatter key — the same reason the neighbouring patterns above
|
|
263
|
+
* use `^` rather than a bare substring test. A value is required after the colon
|
|
264
|
+
* so a bare key with the value on the next line is not counted as a declaration.
|
|
265
|
+
*/
|
|
266
|
+
const COMPATIBILITY_KEY_PATTERN = /^compatibility:[ \t]*\S/m;
|
|
267
|
+
const HARNESS_COMPAT_KEY_PATTERN = /^harness_compat:[ \t]*\S/m;
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* `safety_mode: strict` is a claim made in the frontmatter about a skill whose
|
|
271
|
+
* execution path may mutate something. Nothing checked that the body honours it,
|
|
272
|
+
* so the claim could be pure metadata. These patterns recognise the two shapes
|
|
273
|
+
* the action-reference split allows (`skill-writing` § Action-reference split):
|
|
274
|
+
* an inline precondition that gates the write, or a deferral of the write-path
|
|
275
|
+
* steps into the skill's own `references/`.
|
|
276
|
+
*
|
|
277
|
+
* Deliberately a warning over a phrase list rather than an error over a required
|
|
278
|
+
* section: a skill that gates its write path some third way is doing the right
|
|
279
|
+
* thing, and a hard gate over a corpus of one strict skill would be a gate that
|
|
280
|
+
* mostly cannot fail — the defect class `road-to-skill-ecosystem-gate-integrity`
|
|
281
|
+
* exists to remove.
|
|
282
|
+
*/
|
|
283
|
+
const STRICT_WRITE_GATE_PATTERNS = [
|
|
284
|
+
/--dry-run/i,
|
|
285
|
+
/\bnever silent[- ]run/i,
|
|
286
|
+
/\bpropose,? never\b/i,
|
|
287
|
+
/\bconfirms? before\b/i,
|
|
288
|
+
/\bexplicit (?:user )?confirmation\b/i,
|
|
289
|
+
/\basks? (?:the user )?(?:first|before)\b/i,
|
|
290
|
+
];
|
|
291
|
+
const SKILL_REFERENCE_DEFERRAL_PATTERN = /\]\(\.{0,2}\/?references\//;
|
|
292
|
+
|
|
257
293
|
const VALID_EXECUTION_FIELDS = new Set([
|
|
258
294
|
'type',
|
|
259
295
|
'handler',
|
|
@@ -1295,7 +1331,7 @@ function lint_wing4_boundaries(text: string): Issue[] {
|
|
|
1295
1331
|
* for an optional parameter — a future caller that forgets it gets a confident
|
|
1296
1332
|
* error about a declaration it never looked for.
|
|
1297
1333
|
*/
|
|
1298
|
-
function lint_execution_metadata(execution: ExecutionBlock, frontmatter: string): Issue[] {
|
|
1334
|
+
function lint_execution_metadata(execution: ExecutionBlock, frontmatter: string, body = ''): Issue[] {
|
|
1299
1335
|
const issues: Issue[] = [];
|
|
1300
1336
|
|
|
1301
1337
|
const execType = execution.type;
|
|
@@ -1370,6 +1406,29 @@ function lint_execution_metadata(execution: ExecutionBlock, frontmatter: string)
|
|
|
1370
1406
|
);
|
|
1371
1407
|
}
|
|
1372
1408
|
|
|
1409
|
+
// road-to-skill-ecosystem-executable-payloads Phase 5 — the action-reference
|
|
1410
|
+
// split. A strict-mode skill either gates its write path inline, or says it
|
|
1411
|
+
// does not define the mutating workflow and defers those steps to its own
|
|
1412
|
+
// `references/` behind a precondition. Neither present → the strict claim is
|
|
1413
|
+
// frontmatter only, and the body reads like an ordinary skill.
|
|
1414
|
+
if (
|
|
1415
|
+
safetyMode === 'strict' &&
|
|
1416
|
+
body !== '' &&
|
|
1417
|
+
!STRICT_WRITE_GATE_PATTERNS.some((p) => p.test(body)) &&
|
|
1418
|
+
!SKILL_REFERENCE_DEFERRAL_PATTERN.test(body)
|
|
1419
|
+
) {
|
|
1420
|
+
issues.push(
|
|
1421
|
+
new Issue(
|
|
1422
|
+
'warning',
|
|
1423
|
+
'strict_mode_missing_write_gate',
|
|
1424
|
+
"execution.safety_mode is 'strict' but the body names no precondition on the write " +
|
|
1425
|
+
'path and defers no write-path steps to the skill\'s references/ — state the gate ' +
|
|
1426
|
+
'inline (propose-then-confirm, --dry-run) or defer it, per skill-writing ' +
|
|
1427
|
+
'§ Action-reference split',
|
|
1428
|
+
),
|
|
1429
|
+
);
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1373
1432
|
const timeout = execution.timeout_seconds;
|
|
1374
1433
|
if (timeout !== undefined) {
|
|
1375
1434
|
if (typeof timeout !== 'number' || !Number.isInteger(timeout) || timeout <= 0) {
|
|
@@ -1583,9 +1642,30 @@ export function lint_skill(p: string, text: string, absPath: string | null = nul
|
|
|
1583
1642
|
}
|
|
1584
1643
|
}
|
|
1585
1644
|
|
|
1645
|
+
// `compatibility:` is deprecated in favour of the structured
|
|
1646
|
+
// `harness_compat:` (schema, 2026-08-14). The field is NOT removed — the
|
|
1647
|
+
// schema still accepts it and both existing users still carry it — so this
|
|
1648
|
+
// is a warning, not an error: its job is to stop NEW adoption during the
|
|
1649
|
+
// window, which is the only thing a deprecation notice with no gate cannot
|
|
1650
|
+
// do. Regex, not a YAML parse, to match the file's existing convention.
|
|
1651
|
+
if (
|
|
1652
|
+
COMPATIBILITY_KEY_PATTERN.test(frontmatter) &&
|
|
1653
|
+
!HARNESS_COMPAT_KEY_PATTERN.test(frontmatter)
|
|
1654
|
+
) {
|
|
1655
|
+
issues.push(
|
|
1656
|
+
new Issue(
|
|
1657
|
+
'warning',
|
|
1658
|
+
'deprecated_compatibility_field',
|
|
1659
|
+
"Frontmatter declares the deprecated free-text 'compatibility:' without the structured " +
|
|
1660
|
+
"'harness_compat:' that supersedes it — add harness_compat (see skill.schema.json and " +
|
|
1661
|
+
'docs/MIGRATION.md § Scheduled deprecations); compatibility stays accepted until removal',
|
|
1662
|
+
),
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1586
1666
|
const execution = parseExecutionBlock(frontmatter);
|
|
1587
1667
|
if (execution !== null) {
|
|
1588
|
-
issues.push(...lint_execution_metadata(execution, frontmatter));
|
|
1668
|
+
issues.push(...lint_execution_metadata(execution, frontmatter, text.replace(FRONTMATTER_PATTERN, '')));
|
|
1589
1669
|
}
|
|
1590
1670
|
|
|
1591
1671
|
const tierMatch = TIER_PATTERN.exec(frontmatter);
|