@ikon85/agent-workflow-kit 0.34.6 → 0.35.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/.agents/skills/ask-matt/SKILL.md +8 -0
- package/.agents/skills/audit-skills/SKILL.md +8 -0
- package/.agents/skills/board-to-waves/SKILL.md +8 -0
- package/.agents/skills/census-update/SKILL.md +8 -0
- package/.agents/skills/code-review/SKILL.md +8 -0
- package/.agents/skills/codebase-design/SKILL.md +8 -0
- package/.agents/skills/codex-adapter-sync/SKILL.md +8 -0
- package/.agents/skills/decision-gate/SKILL.md +8 -0
- package/.agents/skills/diagnose/SKILL.md +8 -0
- package/.agents/skills/domain-modeling/SKILL.md +8 -0
- package/.agents/skills/git-worktree-recover/SKILL.md +8 -0
- package/.agents/skills/grill-me/SKILL.md +8 -0
- package/.agents/skills/grill-with-docs/SKILL.md +8 -0
- package/.agents/skills/implement/SKILL.md +8 -0
- package/.agents/skills/improve-codebase-architecture/SKILL.md +8 -0
- package/.agents/skills/kit-release/SKILL.md +8 -0
- package/.agents/skills/kit-update/SKILL.md +18 -4
- package/.agents/skills/local-ci/SKILL.md +8 -0
- package/.agents/skills/memory-lifecycle/SKILL.md +8 -0
- package/.agents/skills/orchestrate-wave/SKILL.md +2 -2
- package/.agents/skills/project-release/SKILL.md +8 -0
- package/.agents/skills/prototype/SKILL.md +8 -0
- package/.agents/skills/research/SKILL.md +8 -0
- package/.agents/skills/resolving-merge-conflicts/SKILL.md +8 -0
- package/.agents/skills/retro/SKILL.md +8 -0
- package/.agents/skills/scale-check/SKILL.md +8 -0
- package/.agents/skills/security-audit/SKILL.md +8 -0
- package/.agents/skills/setup-workflow/SKILL.md +30 -0
- package/.agents/skills/setup-workflow/orchestrate-wave-seed.md +1 -0
- package/.agents/skills/setup-workflow/spec-self-critique-seed.md +1 -0
- package/.agents/skills/spec-self-critique/SKILL.md +8 -0
- package/.agents/skills/tdd/SKILL.md +8 -0
- package/.agents/skills/to-issues/SKILL.md +8 -0
- package/.agents/skills/to-prd/SKILL.md +8 -0
- package/.agents/skills/to-waves/SKILL.md +8 -0
- package/.agents/skills/triage/SKILL.md +8 -0
- package/.agents/skills/verify-spike/SKILL.md +8 -0
- package/.agents/skills/wayfinder/SKILL.md +8 -0
- package/.agents/skills/wrapup/SKILL.md +8 -0
- package/.claude/skills/ask-matt/SKILL.md +8 -0
- package/.claude/skills/audit-skills/SKILL.md +8 -0
- package/.claude/skills/board-to-waves/SKILL.md +8 -0
- package/.claude/skills/census-update/SKILL.md +8 -0
- package/.claude/skills/code-review/SKILL.md +8 -0
- package/.claude/skills/codebase-design/SKILL.md +8 -0
- package/.claude/skills/codex-build/SKILL.md +8 -0
- package/.claude/skills/codex-review/SKILL.md +8 -0
- package/.claude/skills/decision-gate/SKILL.md +8 -0
- package/.claude/skills/diagnose/SKILL.md +8 -0
- package/.claude/skills/domain-modeling/SKILL.md +8 -0
- package/.claude/skills/git-guardrails-claude-code/SKILL.md +8 -0
- package/.claude/skills/git-worktree-recover/SKILL.md +8 -0
- package/.claude/skills/grill-me/SKILL.md +8 -0
- package/.claude/skills/grill-me-codex/SKILL.md +8 -0
- package/.claude/skills/grill-with-docs/SKILL.md +8 -0
- package/.claude/skills/grill-with-docs-codex/SKILL.md +8 -0
- package/.claude/skills/implement/SKILL.md +8 -0
- package/.claude/skills/improve-codebase-architecture/SKILL.md +8 -0
- package/.claude/skills/kit-release/SKILL.md +8 -0
- package/.claude/skills/kit-update/SKILL.md +18 -4
- package/.claude/skills/local-ci/SKILL.md +8 -0
- package/.claude/skills/memory-lifecycle/SKILL.md +8 -0
- package/.claude/skills/orchestrate-wave/SKILL.md +2 -2
- package/.claude/skills/project-release/SKILL.md +8 -0
- package/.claude/skills/prototype/SKILL.md +8 -0
- package/.claude/skills/research/SKILL.md +8 -0
- package/.claude/skills/resolving-merge-conflicts/SKILL.md +8 -0
- package/.claude/skills/retro/SKILL.md +8 -0
- package/.claude/skills/scale-check/SKILL.md +8 -0
- package/.claude/skills/security-audit/SKILL.md +8 -0
- package/.claude/skills/setup-pre-commit/SKILL.md +8 -0
- package/.claude/skills/setup-workflow/SKILL.md +30 -0
- package/.claude/skills/setup-workflow/orchestrate-wave-seed.md +1 -0
- package/.claude/skills/setup-workflow/spec-self-critique-seed.md +1 -0
- package/.claude/skills/spec-self-critique/SKILL.md +8 -0
- package/.claude/skills/tdd/SKILL.md +8 -0
- package/.claude/skills/to-issues/SKILL.md +8 -0
- package/.claude/skills/to-prd/SKILL.md +8 -0
- package/.claude/skills/to-waves/SKILL.md +8 -0
- package/.claude/skills/triage/SKILL.md +8 -0
- package/.claude/skills/verify-spike/SKILL.md +8 -0
- package/.claude/skills/wayfinder/SKILL.md +8 -0
- package/.claude/skills/wrapup/SKILL.md +8 -0
- package/.claude/skills/write-a-skill/SKILL.md +8 -0
- package/README.md +118 -4
- package/agent-workflow-kit.package.json +127 -87
- package/docs/agents/skills/local-ci.md +1 -0
- package/docs/agents/skills/orchestrate-wave.md +1 -1
- package/docs/agents/skills/spec-self-critique.md +1 -1
- package/package.json +1 -1
- package/scripts/project-skill-extension.mjs +37 -0
- package/scripts/readiness.mjs +2 -1
- package/scripts/test_skill_publish_audit.py +9 -1
- package/src/cli.mjs +29 -12
- package/src/commands/init.mjs +24 -0
- package/src/commands/own.mjs +4 -3
- package/src/commands/update.mjs +86 -21
- package/src/lib/bundle.mjs +5 -0
- package/src/lib/manifest.mjs +13 -2
- package/src/lib/ownershipClassifier.mjs +54 -0
- package/src/lib/projectSkillExtension.mjs +60 -0
- package/src/lib/skillRegistry.mjs +167 -0
- package/src/lib/updateCandidate.mjs +105 -4
- package/src/lib/updateDecisions.mjs +5 -0
- package/src/lib/updateReconcile.mjs +64 -8
- package/src/lib/verifyUpdateCandidate.mjs +13 -1
- package/src/lib/verifyUpdateCandidateProtocol.mjs +2 -0
- package/src/lib/verifyUpdateCandidateTransaction.mjs +9 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<!-- setup-workflow: state=filled -->
|
|
2
|
+
<!-- agent-workflow-kit: project-extension/v1; skill=orchestrate-wave -->
|
|
2
3
|
# Project layer — orchestrate-wave
|
|
3
4
|
|
|
4
5
|
These stable sections are intentionally empty until the kit has proven its own landing recipe. The skill uses its generic fallback meanwhile.
|
|
@@ -10,4 +11,3 @@ These stable sections are intentionally empty until the kit has proven its own l
|
|
|
10
11
|
## §Verify Recipe
|
|
11
12
|
## §Headless Login
|
|
12
13
|
## §Landing
|
|
13
|
-
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<!-- setup-workflow: state=filled -->
|
|
2
|
+
<!-- agent-workflow-kit: project-extension/v1; skill=spec-self-critique -->
|
|
2
3
|
# Project layer — spec-self-critique
|
|
3
4
|
|
|
4
5
|
The generic checklist owns the checks; `/retro` appends project-specific incidents below these stable anchors.
|
|
@@ -15,4 +16,3 @@ The generic checklist owns the checks; `/retro` appends project-specific inciden
|
|
|
15
16
|
## Point 10
|
|
16
17
|
## Point 11
|
|
17
18
|
## Point 12
|
|
18
|
-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ikon85/agent-workflow-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.0",
|
|
4
4
|
"description": "Portable AI-agent workflow skills (plan → execute → land → learn) for Claude Code & Codex — grilling, TDD, diagnosis, two-axis code review, cross-model Codex review, design & domain-modeling, plus a skill router (ask-matt). npx init/update/diff/uninstall.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { inspectProjectSkillExtension } from '../src/lib/projectSkillExtension.mjs';
|
|
5
|
+
|
|
6
|
+
function argumentsFor(argv) {
|
|
7
|
+
const args = [...argv];
|
|
8
|
+
if (args.shift() !== 'inspect') {
|
|
9
|
+
throw new Error('usage: project-skill-extension.mjs inspect --skill <name> [--root <path>] --json');
|
|
10
|
+
}
|
|
11
|
+
const options = { root: process.cwd(), json: false };
|
|
12
|
+
while (args.length) {
|
|
13
|
+
const flag = args.shift();
|
|
14
|
+
if (flag === '--skill') options.skill = args.shift();
|
|
15
|
+
else if (flag === '--root') options.root = resolve(args.shift());
|
|
16
|
+
else if (flag === '--json') options.json = true;
|
|
17
|
+
else throw new Error(`unknown option: ${flag}`);
|
|
18
|
+
}
|
|
19
|
+
if (!options.skill) throw new Error('--skill is required');
|
|
20
|
+
if (!options.json) throw new Error('--json is required');
|
|
21
|
+
return options;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
25
|
+
const options = argumentsFor(argv);
|
|
26
|
+
return inspectProjectSkillExtension(options);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
30
|
+
main().then(
|
|
31
|
+
(result) => console.log(JSON.stringify(result)),
|
|
32
|
+
(error) => {
|
|
33
|
+
console.log(JSON.stringify({ state: 'blocked', diagnostic: error.message }));
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
}
|
package/scripts/readiness.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { join, resolve } from 'node:path';
|
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { firstLineState } from '../src/lib/sentinel.mjs';
|
|
6
6
|
import { CONSUMER_MANIFEST_NAME, readManifest, writeManifest } from '../src/lib/manifest.mjs';
|
|
7
|
+
import { readComposedSkillRegistry } from '../src/lib/skillRegistry.mjs';
|
|
7
8
|
|
|
8
9
|
const SOURCE_MANIFEST = '.claude/skills/skill-manifest.json';
|
|
9
10
|
const DECISIONS = new Set(['pending', 'not-applicable']);
|
|
@@ -176,7 +177,7 @@ export async function evaluateCapability({ root, capability, decision }) {
|
|
|
176
177
|
async function loadManifest(root) {
|
|
177
178
|
const body = await readText(root, SOURCE_MANIFEST);
|
|
178
179
|
if (body === null) throw new Error(`readiness manifest not found: ${SOURCE_MANIFEST}`);
|
|
179
|
-
return JSON.parse(body);
|
|
180
|
+
return readComposedSkillRegistry(root, JSON.parse(body));
|
|
180
181
|
}
|
|
181
182
|
|
|
182
183
|
export async function checkSkill({ root, skill, manifest }) {
|
|
@@ -94,7 +94,7 @@ BARE_PRIVATE = [
|
|
|
94
94
|
]
|
|
95
95
|
PARENT_REF = re.compile(r"\.\./")
|
|
96
96
|
SHIPPED_SRC_IMPORT = re.compile(
|
|
97
|
-
r"^\s*import\b.*\bfrom\s+['\"]\.\./src/lib/[
|
|
97
|
+
r"^\s*import\b.*\bfrom\s+['\"]\.\./src/lib/[A-Za-z0-9-]+\.mjs['\"];\s*$"
|
|
98
98
|
)
|
|
99
99
|
LONG_HEX = re.compile(r"\b[0-9a-f]{40,}\b")
|
|
100
100
|
GH_TOKEN = re.compile(r"\b(?:ghp|gho|ghu|ghs|github_pat)_[A-Za-z0-9_]{20,}")
|
|
@@ -317,6 +317,14 @@ class Exemptions(unittest.TestCase):
|
|
|
317
317
|
encoding="utf-8")
|
|
318
318
|
self.assertEqual(audit_dir(self.dir), [])
|
|
319
319
|
|
|
320
|
+
def test_top_level_script_may_import_camel_case_shipped_deep_module(self):
|
|
321
|
+
p = self.dir / "scripts"
|
|
322
|
+
p.mkdir()
|
|
323
|
+
(p / "readiness.mjs").write_text(
|
|
324
|
+
"import { load } from '../src/lib/skillRegistry.mjs';\n",
|
|
325
|
+
encoding="utf-8")
|
|
326
|
+
self.assertEqual(audit_dir(self.dir), [])
|
|
327
|
+
|
|
320
328
|
|
|
321
329
|
class RealDistKitIsClean(unittest.TestCase):
|
|
322
330
|
"""Build dist-kit/ from SSOT and assert the audit passes (the publish gate)."""
|
package/src/cli.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { diff } from './commands/diff.mjs';
|
|
|
8
8
|
import { uninstall } from './commands/uninstall.mjs';
|
|
9
9
|
import { setOwnership } from './commands/own.mjs';
|
|
10
10
|
import { CONSUMER_ORIGIN, KIT_ORIGIN } from './lib/manifest.mjs';
|
|
11
|
+
import { nonInteractiveUpdateDecision } from './lib/updateDecisions.mjs';
|
|
11
12
|
import { currentAgentSurface } from './lib/agentSurfaceRegistry.mjs';
|
|
12
13
|
import { createCommandAdapter } from '../scripts/release-state.mjs';
|
|
13
14
|
import { installedIdentityFromDir } from '../scripts/release-parity.mjs';
|
|
@@ -25,6 +26,7 @@ const cmd = args[0];
|
|
|
25
26
|
const force = args.includes('--force');
|
|
26
27
|
const yes = args.includes('--yes') || args.includes('-y');
|
|
27
28
|
const owned = args.includes('--owned');
|
|
29
|
+
const ownershipState = args.find((arg) => arg.startsWith('--as='))?.slice('--as='.length);
|
|
28
30
|
|
|
29
31
|
p.intro('agent-workflow-kit');
|
|
30
32
|
|
|
@@ -49,7 +51,9 @@ try {
|
|
|
49
51
|
printPlan(r);
|
|
50
52
|
p.outro('Dry run — nothing written. Run `update` to apply.');
|
|
51
53
|
} else if (cmd === 'update') {
|
|
52
|
-
const decide = (action, path) =>
|
|
54
|
+
const decide = (action, path, classification) => (
|
|
55
|
+
decideUpdate(action, path, yes, classification)
|
|
56
|
+
);
|
|
53
57
|
const releaseIdentities = await readUpdateRelease();
|
|
54
58
|
const r = await update({
|
|
55
59
|
kitRoot: KIT_ROOT,
|
|
@@ -65,7 +69,10 @@ try {
|
|
|
65
69
|
if (r.state === 'failed') throw new Error(renderUpdateFailure(r));
|
|
66
70
|
if (r.state === 'conflicted') {
|
|
67
71
|
p.note(r.report.recommendation, 'recommendation');
|
|
68
|
-
p.outro(
|
|
72
|
+
p.outro(
|
|
73
|
+
`not applied · conflicts ${r.conflicts.length} · ` +
|
|
74
|
+
`ownership collisions ${r.collisions.length}`,
|
|
75
|
+
);
|
|
69
76
|
process.exitCode = 2;
|
|
70
77
|
} else if (r.status === 'current') {
|
|
71
78
|
p.outro(`aktuell · unchanged ${r.unchanged.length} · local modifications ${r.userModified.length}`);
|
|
@@ -78,12 +85,19 @@ try {
|
|
|
78
85
|
const r = await uninstall({ consumerRoot });
|
|
79
86
|
p.outro(`removed ${r.removed.length} · retained (edited/referenced) ${r.retained.length}`);
|
|
80
87
|
} else if (cmd === 'own' || cmd === 'disown') {
|
|
81
|
-
if (!args[1])
|
|
88
|
+
if (!args[1]) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Usage: agent-workflow-kit ${cmd} <path>` +
|
|
91
|
+
(cmd === 'own' ? ' [--as=contribution-bridge|explicit-fork]' : ''),
|
|
92
|
+
);
|
|
93
|
+
}
|
|
82
94
|
const origin = cmd === 'own' ? CONSUMER_ORIGIN : KIT_ORIGIN;
|
|
83
|
-
await setOwnership({ consumerRoot, path: args[1], origin });
|
|
84
|
-
p.outro(`${args[1]} is now ${origin}-owned`
|
|
95
|
+
await setOwnership({ consumerRoot, path: args[1], origin, ownershipState });
|
|
96
|
+
p.outro(`${args[1]} is now ${origin}-owned` +
|
|
97
|
+
(origin === CONSUMER_ORIGIN ? ` (${ownershipState ?? 'explicit-fork'})` : ''));
|
|
85
98
|
} else {
|
|
86
|
-
p.note('Usage: agent-workflow-kit <init|update|diff|uninstall|own|disown>
|
|
99
|
+
p.note('Usage: agent-workflow-kit <init|update|diff|uninstall|own|disown> ' +
|
|
100
|
+
'[<path>] [--force] [--yes] [--owned] [--as=contribution-bridge|explicit-fork]');
|
|
87
101
|
p.outro('');
|
|
88
102
|
}
|
|
89
103
|
} catch (err) {
|
|
@@ -119,18 +133,21 @@ function printPlan(r) {
|
|
|
119
133
|
p.note(lines.join('\n') || 'no changes', 'plan');
|
|
120
134
|
}
|
|
121
135
|
|
|
122
|
-
async function decideUpdate(action, path, yes) {
|
|
136
|
+
async function decideUpdate(action, path, yes, classification) {
|
|
137
|
+
if (yes) return nonInteractiveUpdateDecision(action);
|
|
123
138
|
if (action === 'delete') {
|
|
124
|
-
if (yes) return true;
|
|
125
139
|
return (await p.confirm({ message: `Upstream removed ${path} — delete it locally?` })) === true;
|
|
126
140
|
}
|
|
127
141
|
if (action === 'collision') {
|
|
128
|
-
if (yes) return 'replace';
|
|
129
142
|
const choice = await p.select({
|
|
130
|
-
message: `${path}
|
|
143
|
+
message: `${path} exists without ownership evidence. Choose an explicit route:`,
|
|
131
144
|
options: [
|
|
132
|
-
|
|
133
|
-
|
|
145
|
+
...(classification?.state === 'project-extension'
|
|
146
|
+
? [{ value: 'project-extension', label: 'Keep as declared Project extension' }]
|
|
147
|
+
: []),
|
|
148
|
+
{ value: 'contribution-bridge', label: 'Keep as temporary contribution bridge' },
|
|
149
|
+
{ value: 'explicit-fork', label: 'Keep as explicit fork' },
|
|
150
|
+
{ value: 'replace', label: 'Replace explicitly with Kit Core' },
|
|
134
151
|
],
|
|
135
152
|
});
|
|
136
153
|
if (p.isCancel(choice)) throw new Error(`collision decision cancelled for ${path}`);
|
package/src/commands/init.mjs
CHANGED
|
@@ -9,9 +9,13 @@ import {
|
|
|
9
9
|
filesForInstallRole, CONSUMER_INSTALL_ROLE,
|
|
10
10
|
indexByPath,
|
|
11
11
|
readReadinessContract,
|
|
12
|
+
READINESS_MANIFEST_PATH,
|
|
12
13
|
CONSUMER_ORIGIN,
|
|
13
14
|
PACKAGE_MANIFEST_NAME, CONSUMER_MANIFEST_NAME,
|
|
14
15
|
} from '../lib/manifest.mjs';
|
|
16
|
+
import {
|
|
17
|
+
PROJECT_SKILL_REGISTRY_PATH, emptyProjectSkillRegistry,
|
|
18
|
+
} from '../lib/skillRegistry.mjs';
|
|
15
19
|
import {
|
|
16
20
|
inspectRoutingProfile, reconcileRoutingProfile, setupRoutingProfile,
|
|
17
21
|
} from '../lib/routingProfile.mjs';
|
|
@@ -71,6 +75,26 @@ export async function init({ kitRoot, consumerRoot, force = false, routingProfil
|
|
|
71
75
|
result.copied.push(f.path);
|
|
72
76
|
}
|
|
73
77
|
|
|
78
|
+
if (packageIdx.has(READINESS_MANIFEST_PATH)
|
|
79
|
+
&& !installed.some(({ path }) => path === PROJECT_SKILL_REGISTRY_PATH)) {
|
|
80
|
+
const projectRegistryPath = join(consumerRoot, PROJECT_SKILL_REGISTRY_PATH);
|
|
81
|
+
if (!await exists(projectRegistryPath)) {
|
|
82
|
+
const core = await readManifest(join(kitRoot, READINESS_MANIFEST_PATH));
|
|
83
|
+
await writeAtomic(
|
|
84
|
+
projectRegistryPath,
|
|
85
|
+
`${JSON.stringify(emptyProjectSkillRegistry(core), null, 2)}\n`,
|
|
86
|
+
);
|
|
87
|
+
result.seeded.push(PROJECT_SKILL_REGISTRY_PATH);
|
|
88
|
+
}
|
|
89
|
+
installed.push({
|
|
90
|
+
path: PROJECT_SKILL_REGISTRY_PATH,
|
|
91
|
+
kind: 'doc',
|
|
92
|
+
installedSha256: await sha256File(projectRegistryPath),
|
|
93
|
+
origin: CONSUMER_ORIGIN,
|
|
94
|
+
installRole: CONSUMER_INSTALL_ROLE,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
74
98
|
await writeManifest(
|
|
75
99
|
join(consumerRoot, CONSUMER_MANIFEST_NAME),
|
|
76
100
|
{ ...emptyConsumerManifest(pkg.kitVersion, prior, readiness), installed }
|
package/src/commands/own.mjs
CHANGED
|
@@ -5,12 +5,13 @@ import {
|
|
|
5
5
|
} from '../lib/manifest.mjs';
|
|
6
6
|
|
|
7
7
|
/** Mark one tracked consumer file as kit- or consumer-owned. */
|
|
8
|
-
export async function setOwnership({ consumerRoot, path, origin }) {
|
|
8
|
+
export async function setOwnership({ consumerRoot, path, origin, ownershipState }) {
|
|
9
9
|
const manifestPath = join(consumerRoot, CONSUMER_MANIFEST_NAME);
|
|
10
10
|
const manifest = await readManifest(manifestPath);
|
|
11
11
|
if (!manifest) throw new Error('not initialised — run `init` first');
|
|
12
|
-
const next = withOrigin(manifest, path, origin);
|
|
12
|
+
const next = withOrigin(manifest, path, origin, ownershipState);
|
|
13
13
|
await validateConsumerFile(consumerRoot, path);
|
|
14
14
|
await writeManifest(manifestPath, next);
|
|
15
|
-
return { path, origin
|
|
15
|
+
return { path, origin, ownershipState: origin === 'consumer'
|
|
16
|
+
? (ownershipState ?? 'explicit-fork') : undefined };
|
|
16
17
|
}
|
package/src/commands/update.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from '../lib/verifyUpdateCandidate.mjs';
|
|
10
10
|
import { reconcile } from '../lib/updateReconcile.mjs';
|
|
11
11
|
import {
|
|
12
|
-
CONSUMER_MANIFEST_NAME, PACKAGE_MANIFEST_NAME, readManifest,
|
|
12
|
+
CONSUMER_MANIFEST_NAME, PACKAGE_MANIFEST_NAME, PROJECT_SKILL_REGISTRY_PATH, readManifest,
|
|
13
13
|
} from '../lib/manifest.mjs';
|
|
14
14
|
import {
|
|
15
15
|
inspectRoutingProfile, reconcileRoutingProfile,
|
|
@@ -81,11 +81,7 @@ async function updatePackage(options) {
|
|
|
81
81
|
Object.assign(preview, await previewReadinessAdoption({
|
|
82
82
|
kitRoot, consumerRoot, pkg, priorReadinessManifest, nextReadinessManifest,
|
|
83
83
|
}));
|
|
84
|
-
preview.conflicts.push(...(preview.migrationConflicts ?? []).map(
|
|
85
|
-
path,
|
|
86
|
-
kind: 'prod-section',
|
|
87
|
-
diff: 'Prod section differs from the other instruction surface or is malformed.',
|
|
88
|
-
})));
|
|
84
|
+
preview.conflicts.push(...(preview.migrationConflicts ?? []).map(migrationConflictRecord));
|
|
89
85
|
} catch (error) {
|
|
90
86
|
previewFailure = error;
|
|
91
87
|
}
|
|
@@ -104,6 +100,9 @@ async function updatePackage(options) {
|
|
|
104
100
|
const resolvedPreview = await resolvePreview({
|
|
105
101
|
kitRoot, consumerRoot, preview, decisions, decide, transition,
|
|
106
102
|
});
|
|
103
|
+
if (resolvedPreview.collisions.length) {
|
|
104
|
+
return terminal(resolvedPreview, 'conflicted', history, transition);
|
|
105
|
+
}
|
|
107
106
|
if (resolvedPreview.conflicts.length) {
|
|
108
107
|
return terminal(resolvedPreview, 'conflicted', history, transition);
|
|
109
108
|
}
|
|
@@ -129,13 +128,19 @@ async function previewReadinessAdoption(context) {
|
|
|
129
128
|
kitRoot, consumerRoot: candidateRoot,
|
|
130
129
|
decide: (action) => action === 'collision' ? 'keep-as-owned' : false,
|
|
131
130
|
});
|
|
132
|
-
await
|
|
133
|
-
|
|
134
|
-
});
|
|
135
|
-
return await adoptReadinessCandidate({
|
|
136
|
-
candidateRoot, consumerRoot, priorManifest: priorReadinessManifest,
|
|
131
|
+
const readiness = await adoptReadinessCandidate({
|
|
132
|
+
candidateRoot, consumerRoot, kitRoot, priorManifest: priorReadinessManifest,
|
|
137
133
|
nextManifest: nextReadinessManifest,
|
|
138
134
|
});
|
|
135
|
+
candidatePreview.generated = readiness.generated;
|
|
136
|
+
candidatePreview.migrations = readiness.migrations;
|
|
137
|
+
candidatePreview.migrated = readiness.migrated;
|
|
138
|
+
if (!readiness.migrationConflicts.length) {
|
|
139
|
+
await verifyCandidateSchema(candidateRoot, {
|
|
140
|
+
pkg, preview: candidatePreview, priorReadinessManifest, nextReadinessManifest,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return readiness;
|
|
139
144
|
} finally {
|
|
140
145
|
await rm(candidateRoot, { recursive: true, force: true });
|
|
141
146
|
}
|
|
@@ -146,7 +151,13 @@ async function resolvePreview({ kitRoot, consumerRoot, preview, decisions, decid
|
|
|
146
151
|
await transition('awaiting_decision');
|
|
147
152
|
}
|
|
148
153
|
for (const path of preview.collisions) {
|
|
149
|
-
|
|
154
|
+
const classification = preview.ownershipStates?.find(
|
|
155
|
+
(candidate) => candidate.path === path,
|
|
156
|
+
);
|
|
157
|
+
decisions.set(
|
|
158
|
+
decisionKey('collision', path),
|
|
159
|
+
await decide('collision', path, classification),
|
|
160
|
+
);
|
|
150
161
|
}
|
|
151
162
|
if (!preview.collisions.length) return preview;
|
|
152
163
|
return reconcile({
|
|
@@ -192,9 +203,8 @@ async function applyTransaction(context) {
|
|
|
192
203
|
priorReadinessManifest: structuredClone(priorReadinessManifest),
|
|
193
204
|
nextReadinessManifest: structuredClone(nextReadinessManifest),
|
|
194
205
|
};
|
|
195
|
-
await verifyCandidateSchema(candidateRoot, canonicalContext);
|
|
196
206
|
const readiness = await adoptReadinessCandidate({
|
|
197
|
-
candidateRoot, consumerRoot, priorManifest: priorReadinessManifest,
|
|
207
|
+
candidateRoot, consumerRoot, kitRoot, priorManifest: priorReadinessManifest,
|
|
198
208
|
nextManifest: nextReadinessManifest,
|
|
199
209
|
});
|
|
200
210
|
preview.generated = readiness.generated;
|
|
@@ -203,12 +213,13 @@ async function applyTransaction(context) {
|
|
|
203
213
|
preview.migrationConflicts = readiness.migrationConflicts;
|
|
204
214
|
preview.availability = readiness.availability;
|
|
205
215
|
if (readiness.migrationConflicts.length) {
|
|
206
|
-
throw new Error(`
|
|
216
|
+
throw new Error(`readiness migration conflict: ${readiness.migrationConflicts.join(', ')}`);
|
|
207
217
|
}
|
|
208
218
|
if (readiness.incompatible.length) {
|
|
209
219
|
throw new Error(`monotonic compatibility would block existing skill core: ${readiness.incompatible.join(', ')}`);
|
|
210
220
|
}
|
|
211
221
|
canonicalContext.preview = structuredClone(preview);
|
|
222
|
+
await verifyCandidateSchema(candidateRoot, canonicalContext);
|
|
212
223
|
await verifyUpdateCandidate(candidateRoot, canonicalContext);
|
|
213
224
|
if (verify !== verifyUpdateCandidate) {
|
|
214
225
|
const extensionContext = structuredClone(canonicalContext);
|
|
@@ -274,14 +285,68 @@ async function terminal(result, state, history, transition) {
|
|
|
274
285
|
conflicts: result.conflicts.map(({ path }) => path),
|
|
275
286
|
keptDeleted: result.keptDeleted,
|
|
276
287
|
},
|
|
277
|
-
recommendation: result
|
|
278
|
-
? `Prod sections differ or are malformed in: ${result.migrationConflicts.join(', ')}. ` +
|
|
279
|
-
'Resolve them manually; no consumer file was changed.'
|
|
280
|
-
: (result.conflicts.length
|
|
281
|
-
? 'Review each named conflict; keep the local file or apply the incoming diff manually.'
|
|
282
|
-
: null),
|
|
288
|
+
recommendation: updateRecommendation(result),
|
|
283
289
|
},
|
|
284
290
|
};
|
|
285
291
|
}
|
|
286
292
|
|
|
293
|
+
function updateRecommendation(result) {
|
|
294
|
+
if (result.migrationConflicts?.length) {
|
|
295
|
+
return migrationRecommendation(result.migrationConflicts);
|
|
296
|
+
}
|
|
297
|
+
if (result.collisions.length
|
|
298
|
+
|| result.conflicts.some(({ kind }) => kind === 'ownership-lifecycle')) {
|
|
299
|
+
return ownershipRecommendation(result);
|
|
300
|
+
}
|
|
301
|
+
return result.conflicts.length
|
|
302
|
+
? 'Review each named conflict; keep the local file or apply the incoming diff manually.'
|
|
303
|
+
: null;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function migrationConflictRecord(detail) {
|
|
307
|
+
const path = detail.split(': ', 1)[0];
|
|
308
|
+
const projectRegistry = path === PROJECT_SKILL_REGISTRY_PATH
|
|
309
|
+
|| path === '.claude/skills/skill-manifest.json';
|
|
310
|
+
return {
|
|
311
|
+
path,
|
|
312
|
+
kind: projectRegistry ? 'skill-registry' : 'prod-section',
|
|
313
|
+
diff: projectRegistry
|
|
314
|
+
? `Skill registry migration is ambiguous: ${detail}`
|
|
315
|
+
: 'Prod section differs from the other instruction surface or is malformed.',
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function migrationRecommendation(conflicts) {
|
|
320
|
+
const registryConflict = conflicts.some((detail) => (
|
|
321
|
+
detail.startsWith(`${PROJECT_SKILL_REGISTRY_PATH}:`)
|
|
322
|
+
|| detail.startsWith('.claude/skills/skill-manifest.json:')
|
|
323
|
+
));
|
|
324
|
+
return (registryConflict
|
|
325
|
+
? `Readiness migration is ambiguous in: ${conflicts.join(', ')}. `
|
|
326
|
+
: `Prod sections differ or are malformed in: ${conflicts.join(', ')}. `) +
|
|
327
|
+
'Resolve them manually; no consumer file was changed.';
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function ownershipRecommendation(result) {
|
|
331
|
+
const paths = new Set([
|
|
332
|
+
...result.collisions,
|
|
333
|
+
...result.conflicts.filter(({ kind }) => kind === 'ownership-lifecycle')
|
|
334
|
+
.map(({ path }) => path),
|
|
335
|
+
]);
|
|
336
|
+
const unresolved = (result.ownershipStates ?? []).filter(
|
|
337
|
+
({ path }) => paths.has(path),
|
|
338
|
+
);
|
|
339
|
+
return unresolved.map(({ path, state, evidence, routes }) => (
|
|
340
|
+
`${path}: ${state} ` +
|
|
341
|
+
`(package=${evidence.packageDeclared ? 'declared' : 'absent'}, ` +
|
|
342
|
+
`ledger=${evidence.ledgerOrigin}, destination=${evidence.destination}, ` +
|
|
343
|
+
`extension=${evidence.projectExtension}` +
|
|
344
|
+
(evidence.extensionDiagnostic
|
|
345
|
+
? `, extensionDiagnostic=${evidence.extensionDiagnostic}` : '') +
|
|
346
|
+
'); explicit routes: ' +
|
|
347
|
+
routes.map(({ id, action }) => `${id} (${action})`).join(', ')
|
|
348
|
+
)).join('\n') +
|
|
349
|
+
'. No consumer file was changed; --yes cannot choose a route.';
|
|
350
|
+
}
|
|
351
|
+
|
|
287
352
|
export { verifyUpdateCandidate, verifyUpdateCandidate as verifyCandidate } from '../lib/verifyUpdateCandidate.mjs';
|
package/src/lib/bundle.mjs
CHANGED
|
@@ -8,6 +8,7 @@ export const HELPER_FILES = [
|
|
|
8
8
|
// together; the manifest remains the only capability/dependency registry.
|
|
9
9
|
{ path: '.claude/skills/skill-manifest.json', kind: 'doc', mode: 0o644 },
|
|
10
10
|
{ path: 'scripts/readiness.mjs', kind: 'script', mode: 0o755 },
|
|
11
|
+
{ path: 'scripts/project-skill-extension.mjs', kind: 'script', mode: 0o755 },
|
|
11
12
|
{ path: 'src/lib/sentinel.mjs', kind: 'script', mode: 0o644 },
|
|
12
13
|
{ path: 'src/lib/manifest.mjs', kind: 'script', mode: 0o644 },
|
|
13
14
|
{ path: 'src/lib/atomicWrite.mjs', kind: 'script', mode: 0o644 },
|
|
@@ -77,6 +78,10 @@ export const HELPER_FILES = [
|
|
|
77
78
|
{ path: 'src/lib/reportValidator.mjs', kind: 'script', mode: 0o644 },
|
|
78
79
|
// Fail-closed orchestration selector and its host-inventory adapters.
|
|
79
80
|
{ path: 'src/lib/capabilityMatrix.mjs', kind: 'script', mode: 0o644 },
|
|
81
|
+
{ path: 'src/lib/skillRegistry.mjs', kind: 'script', mode: 0o644 },
|
|
82
|
+
{ path: 'src/lib/projectSkillExtension.mjs', kind: 'script', mode: 0o644 },
|
|
83
|
+
{ path: 'src/lib/ownershipClassifier.mjs', kind: 'script', mode: 0o644 },
|
|
84
|
+
{ path: 'src/lib/updateDecisions.mjs', kind: 'script', mode: 0o644 },
|
|
80
85
|
// Provider-neutral routing runtime: one Evidence catalog, Access graph,
|
|
81
86
|
// Routing policy, resolver, spawn guard, receipt v2, and surface attestations.
|
|
82
87
|
// These modules form one consumer unit; omitting one leaves shipped dispatch
|
package/src/lib/manifest.mjs
CHANGED
|
@@ -18,6 +18,7 @@ export const KIT_ORIGIN = 'kit';
|
|
|
18
18
|
export const CONSUMER_ORIGIN = 'consumer';
|
|
19
19
|
export const READINESS_CONTRACT_VERSION = 1;
|
|
20
20
|
export const READINESS_MANIFEST_PATH = '.claude/skills/skill-manifest.json';
|
|
21
|
+
export { PROJECT_SKILL_REGISTRY_PATH } from './skillRegistry.mjs';
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Parse a JSON manifest, or null if the file does not exist. A corrupt file
|
|
@@ -94,7 +95,7 @@ export function indexByPath(manifest, key) {
|
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
/** Return a manifest with one tracked entry moved to the requested ownership state. */
|
|
97
|
-
export function withOrigin(manifest, path, origin) {
|
|
98
|
+
export function withOrigin(manifest, path, origin, ownershipState = 'explicit-fork') {
|
|
98
99
|
if (![KIT_ORIGIN, CONSUMER_ORIGIN].includes(origin)) {
|
|
99
100
|
throw new Error(`invalid manifest origin: ${origin}`);
|
|
100
101
|
}
|
|
@@ -105,6 +106,16 @@ export function withOrigin(manifest, path, origin) {
|
|
|
105
106
|
}
|
|
106
107
|
return {
|
|
107
108
|
...manifest,
|
|
108
|
-
installed: manifest.installed.map((entry) =>
|
|
109
|
+
installed: manifest.installed.map((entry) => {
|
|
110
|
+
if (entry.path !== path) return entry;
|
|
111
|
+
if (origin === KIT_ORIGIN) {
|
|
112
|
+
const { ownershipState: _removed, ...core } = entry;
|
|
113
|
+
return { ...core, origin };
|
|
114
|
+
}
|
|
115
|
+
if (!['contribution-bridge', 'explicit-fork'].includes(ownershipState)) {
|
|
116
|
+
throw new Error(`invalid consumer ownership state: ${ownershipState}`);
|
|
117
|
+
}
|
|
118
|
+
return { ...entry, origin, ownershipState };
|
|
119
|
+
}),
|
|
109
120
|
};
|
|
110
121
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export const OwnershipState = Object.freeze({
|
|
2
|
+
CLEAN_CORE: 'clean-core',
|
|
3
|
+
PROJECT_EXTENSION: 'project-extension',
|
|
4
|
+
CONTRIBUTION_BRIDGE: 'contribution-bridge',
|
|
5
|
+
EXPLICIT_FORK: 'explicit-fork',
|
|
6
|
+
AMBIGUOUS_COLLISION: 'ambiguous-collision',
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const ROUTES = Object.freeze([
|
|
10
|
+
{ id: 'project-extension', action: 'move Project data to docs/agents/skills/<skill>.md' },
|
|
11
|
+
{ id: 'contribution-bridge', action: 'register a temporary contribution-bridge' },
|
|
12
|
+
{ id: 'explicit-fork', action: 'register an explicit-fork with its own update line' },
|
|
13
|
+
{ id: 'clean-core', action: 'explicitly replace the destination with Kit Core' },
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
export function classifyOwnershipEvidence({
|
|
17
|
+
path, packageEntry, installedEntry, destinationPresent, projectExtension,
|
|
18
|
+
}) {
|
|
19
|
+
const evidence = {
|
|
20
|
+
packageDeclared: Boolean(packageEntry),
|
|
21
|
+
ledgerOrigin: installedEntry?.origin ?? 'absent',
|
|
22
|
+
destination: destinationPresent ? 'present' : 'absent',
|
|
23
|
+
projectExtension: projectExtension
|
|
24
|
+
? (projectExtension.invalid ? 'invalid' : `schema-v${projectExtension.schemaVersion}`)
|
|
25
|
+
: 'absent',
|
|
26
|
+
};
|
|
27
|
+
if (projectExtension?.invalid) evidence.extensionDiagnostic = projectExtension.invalid;
|
|
28
|
+
if (installedEntry?.origin === 'kit') {
|
|
29
|
+
return { path, state: OwnershipState.CLEAN_CORE, evidence, routes: [] };
|
|
30
|
+
}
|
|
31
|
+
if (installedEntry?.origin === 'consumer') {
|
|
32
|
+
const state = installedEntry.ownershipState ?? OwnershipState.EXPLICIT_FORK;
|
|
33
|
+
if (![
|
|
34
|
+
OwnershipState.PROJECT_EXTENSION,
|
|
35
|
+
OwnershipState.CONTRIBUTION_BRIDGE,
|
|
36
|
+
OwnershipState.EXPLICIT_FORK,
|
|
37
|
+
].includes(state)) {
|
|
38
|
+
return { path, state: OwnershipState.AMBIGUOUS_COLLISION, evidence, routes: [...ROUTES] };
|
|
39
|
+
}
|
|
40
|
+
if (state === OwnershipState.PROJECT_EXTENSION
|
|
41
|
+
&& (!/^docs\/agents\/skills\/[a-z0-9-]+\.md$/.test(path)
|
|
42
|
+
|| !projectExtension || projectExtension.invalid)) {
|
|
43
|
+
return { path, state: OwnershipState.AMBIGUOUS_COLLISION, evidence, routes: [...ROUTES] };
|
|
44
|
+
}
|
|
45
|
+
return { path, state, evidence, routes: [] };
|
|
46
|
+
}
|
|
47
|
+
if (projectExtension && !projectExtension.invalid) {
|
|
48
|
+
return { path, state: OwnershipState.PROJECT_EXTENSION, evidence, routes: [
|
|
49
|
+
{ id: 'project-extension', action: 'keep the declared Project extension' },
|
|
50
|
+
{ id: 'clean-core', action: 'explicitly replace the destination with Kit Core' },
|
|
51
|
+
] };
|
|
52
|
+
}
|
|
53
|
+
return { path, state: OwnershipState.AMBIGUOUS_COLLISION, evidence, routes: [...ROUTES] };
|
|
54
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { lstat, readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { firstLineState } from './sentinel.mjs';
|
|
4
|
+
|
|
5
|
+
const SKILL_NAME = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
6
|
+
const MARKER_PREFIX = '<!-- agent-workflow-kit: project-extension/';
|
|
7
|
+
const MARKER = /^<!-- agent-workflow-kit: project-extension\/([^;]+); skill=([a-z0-9-]+) -->$/;
|
|
8
|
+
|
|
9
|
+
export function projectSkillExtensionPath(skill) {
|
|
10
|
+
if (typeof skill !== 'string' || !SKILL_NAME.test(skill)) {
|
|
11
|
+
throw new Error(`invalid Project extension skill identity: ${skill}`);
|
|
12
|
+
}
|
|
13
|
+
return `docs/agents/skills/${skill}.md`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function inspectProjectSkillExtension({ root, skill }) {
|
|
17
|
+
const path = projectSkillExtensionPath(skill);
|
|
18
|
+
const absolute = join(root, path);
|
|
19
|
+
let state;
|
|
20
|
+
try {
|
|
21
|
+
state = await lstat(absolute);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (error.code === 'ENOENT') return { state: 'inactive', reason: 'absent' };
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
if (!state.isFile()) {
|
|
27
|
+
throw new Error(`Project extension is not a regular file: ${path}`);
|
|
28
|
+
}
|
|
29
|
+
const body = await readFile(absolute, 'utf8');
|
|
30
|
+
const setupState = firstLineState(body);
|
|
31
|
+
if (setupState === 'stub' || setupState === 'not-applicable') {
|
|
32
|
+
return { state: 'inactive', reason: setupState };
|
|
33
|
+
}
|
|
34
|
+
const lines = body.split('\n').map((line) => line.trim());
|
|
35
|
+
const markerLine = lines.find((line) => line.startsWith(MARKER_PREFIX));
|
|
36
|
+
if (!markerLine) {
|
|
37
|
+
if (!body.trim()) return { state: 'inactive', reason: 'empty' };
|
|
38
|
+
return { state: 'active', schemaVersion: 0, path };
|
|
39
|
+
}
|
|
40
|
+
const match = MARKER.exec(markerLine);
|
|
41
|
+
if (!match || match[1] !== 'v1') {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Project extension has unsupported schema at ${path}; ` +
|
|
44
|
+
`expected project-extension/v1 for skill=${skill}`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
if (match[2] !== skill) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Project extension identity mismatch at ${path}; expected skill=${skill}, ` +
|
|
50
|
+
`found skill=${match[2]}`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
const content = lines.filter(
|
|
54
|
+
(line) => line && line !== markerLine && !line.startsWith('<!-- setup-workflow:'),
|
|
55
|
+
);
|
|
56
|
+
if (!content.length) {
|
|
57
|
+
throw new Error(`Project extension has no instructions after its v1 marker: ${path}`);
|
|
58
|
+
}
|
|
59
|
+
return { state: 'active', schemaVersion: 1, path };
|
|
60
|
+
}
|