@ikon85/agent-workflow-kit 0.34.6 → 0.36.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 +32 -11
- 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 +19 -3
- 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 +52 -1
- package/.agents/skills/setup-workflow/contribution-routing.md +52 -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/hooks/kit-origin-edit-hint.py +18 -13
- 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 +32 -11
- 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 +19 -3
- 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 +52 -1
- package/.claude/skills/setup-workflow/contribution-routing.md +52 -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 +172 -5
- package/agent-workflow-kit.package.json +149 -89
- 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/find-by-marker.py +2 -0
- package/scripts/project-skill-extension.mjs +37 -0
- package/scripts/readiness.mjs +2 -1
- package/scripts/test_marker_lib.py +8 -0
- package/scripts/test_retro_wrapup_contract.py +6 -4
- package/scripts/test_skill_publish_audit.py +9 -1
- package/src/cli.mjs +86 -12
- package/src/commands/init.mjs +24 -0
- package/src/commands/own.mjs +7 -3
- package/src/commands/update.mjs +87 -21
- package/src/lib/bundle.mjs +5 -0
- package/src/lib/contributionBridge.mjs +176 -0
- package/src/lib/contributionRouting.mjs +152 -0
- package/src/lib/manifest.mjs +17 -2
- package/src/lib/ownershipClassifier.mjs +65 -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 +102 -9
- package/src/lib/verifyUpdateCandidate.mjs +28 -1
- package/src/lib/verifyUpdateCandidateProtocol.mjs +2 -0
- package/src/lib/verifyUpdateCandidateTransaction.mjs +19 -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.36.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": {
|
|
@@ -29,6 +29,8 @@ def _gh(args: list[str]) -> str:
|
|
|
29
29
|
except subprocess.TimeoutExpired as exc:
|
|
30
30
|
raise RuntimeError(
|
|
31
31
|
f"gh api timed out after {GH_TIMEOUT_SECONDS}s") from exc
|
|
32
|
+
except OSError as exc:
|
|
33
|
+
raise RuntimeError(f"gh CLI unavailable: {exc.strerror or exc}") from exc
|
|
32
34
|
if result.returncode != 0:
|
|
33
35
|
raise RuntimeError(result.stderr.strip() or "gh api failed")
|
|
34
36
|
return result.stdout
|
|
@@ -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 }) {
|
|
@@ -149,6 +149,14 @@ class MarkerLookupTest(unittest.TestCase):
|
|
|
149
149
|
with self.assertRaisesRegex(RuntimeError, "timed out after 30s"):
|
|
150
150
|
cli._gh(["api", "--paginate", "repos/example/repo/issues"])
|
|
151
151
|
|
|
152
|
+
def test_cli_classifies_a_missing_github_binary(self):
|
|
153
|
+
with patch.object(
|
|
154
|
+
cli.subprocess, "run",
|
|
155
|
+
side_effect=FileNotFoundError(2, "No such file or directory", "gh"),
|
|
156
|
+
):
|
|
157
|
+
with self.assertRaisesRegex(RuntimeError, "gh CLI unavailable"):
|
|
158
|
+
cli._gh(["api", "--paginate", "repos/example/repo/issues"])
|
|
159
|
+
|
|
152
160
|
|
|
153
161
|
if __name__ == "__main__":
|
|
154
162
|
unittest.main()
|
|
@@ -42,13 +42,15 @@ class RetroEnforcementContract(unittest.TestCase):
|
|
|
42
42
|
for phrase in required:
|
|
43
43
|
self.assertIn(phrase, text)
|
|
44
44
|
|
|
45
|
-
def
|
|
45
|
+
def test_capability_route_and_exact_sanitized_approval_survive(self):
|
|
46
46
|
required = (
|
|
47
47
|
"generic or project-specific",
|
|
48
48
|
"recommend `own`",
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
49
|
+
"contribute status <path> --surface=retro",
|
|
50
|
+
"Missing, disabled, invalid, or unverifiable configuration",
|
|
51
|
+
"never infer maintainer status",
|
|
52
|
+
"sanitized exact preview",
|
|
53
|
+
"separate explicit approval",
|
|
52
54
|
"docs/agents/skills/<skill>.md",
|
|
53
55
|
)
|
|
54
56
|
for surface in SURFACES:
|
|
@@ -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
|
@@ -7,7 +7,14 @@ import { renderUpdateFailure, update } from './commands/update.mjs';
|
|
|
7
7
|
import { diff } from './commands/diff.mjs';
|
|
8
8
|
import { uninstall } from './commands/uninstall.mjs';
|
|
9
9
|
import { setOwnership } from './commands/own.mjs';
|
|
10
|
+
import {
|
|
11
|
+
beginContributionBridge, prepareContributionArtifact,
|
|
12
|
+
} from './lib/contributionBridge.mjs';
|
|
13
|
+
import {
|
|
14
|
+
inspectContributionRouting,
|
|
15
|
+
} from './lib/contributionRouting.mjs';
|
|
10
16
|
import { CONSUMER_ORIGIN, KIT_ORIGIN } from './lib/manifest.mjs';
|
|
17
|
+
import { nonInteractiveUpdateDecision } from './lib/updateDecisions.mjs';
|
|
11
18
|
import { currentAgentSurface } from './lib/agentSurfaceRegistry.mjs';
|
|
12
19
|
import { createCommandAdapter } from '../scripts/release-state.mjs';
|
|
13
20
|
import { installedIdentityFromDir } from '../scripts/release-parity.mjs';
|
|
@@ -25,6 +32,7 @@ const cmd = args[0];
|
|
|
25
32
|
const force = args.includes('--force');
|
|
26
33
|
const yes = args.includes('--yes') || args.includes('-y');
|
|
27
34
|
const owned = args.includes('--owned');
|
|
35
|
+
const ownershipState = args.find((arg) => arg.startsWith('--as='))?.slice('--as='.length);
|
|
28
36
|
|
|
29
37
|
p.intro('agent-workflow-kit');
|
|
30
38
|
|
|
@@ -49,7 +57,9 @@ try {
|
|
|
49
57
|
printPlan(r);
|
|
50
58
|
p.outro('Dry run — nothing written. Run `update` to apply.');
|
|
51
59
|
} else if (cmd === 'update') {
|
|
52
|
-
const decide = (action, path) =>
|
|
60
|
+
const decide = (action, path, classification) => (
|
|
61
|
+
decideUpdate(action, path, yes, classification)
|
|
62
|
+
);
|
|
53
63
|
const releaseIdentities = await readUpdateRelease();
|
|
54
64
|
const r = await update({
|
|
55
65
|
kitRoot: KIT_ROOT,
|
|
@@ -65,7 +75,10 @@ try {
|
|
|
65
75
|
if (r.state === 'failed') throw new Error(renderUpdateFailure(r));
|
|
66
76
|
if (r.state === 'conflicted') {
|
|
67
77
|
p.note(r.report.recommendation, 'recommendation');
|
|
68
|
-
p.outro(
|
|
78
|
+
p.outro(
|
|
79
|
+
`not applied · conflicts ${r.conflicts.length} · ` +
|
|
80
|
+
`ownership collisions ${r.collisions.length}`,
|
|
81
|
+
);
|
|
69
82
|
process.exitCode = 2;
|
|
70
83
|
} else if (r.status === 'current') {
|
|
71
84
|
p.outro(`aktuell · unchanged ${r.unchanged.length} · local modifications ${r.userModified.length}`);
|
|
@@ -77,13 +90,70 @@ try {
|
|
|
77
90
|
if (!ok) { p.cancel('Aborted.'); process.exit(0); }
|
|
78
91
|
const r = await uninstall({ consumerRoot });
|
|
79
92
|
p.outro(`removed ${r.removed.length} · retained (edited/referenced) ${r.retained.length}`);
|
|
93
|
+
} else if (cmd === 'contribute') {
|
|
94
|
+
const action = args[1];
|
|
95
|
+
const path = args[2];
|
|
96
|
+
if (!['start', 'status', 'prepare'].includes(action) || !path) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
'Usage: agent-workflow-kit contribute <start|status|prepare> <path> ' +
|
|
99
|
+
'[--surface=retro|pre-update|guard] ' +
|
|
100
|
+
'[--output=.agent-workflow-kit/contributions/<name>.json]',
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
if (action === 'start') {
|
|
104
|
+
await beginContributionBridge({ kitRoot: KIT_ROOT, consumerRoot, path });
|
|
105
|
+
p.outro(`${path} is now a contribution bridge; no remote was changed`);
|
|
106
|
+
} else if (action === 'status') {
|
|
107
|
+
const surface = args.find((arg) => arg.startsWith('--surface='))
|
|
108
|
+
?.slice('--surface='.length) ?? 'guard';
|
|
109
|
+
const routing = await inspectContributionRouting({
|
|
110
|
+
consumerRoot, path, surface,
|
|
111
|
+
});
|
|
112
|
+
p.note(
|
|
113
|
+
[
|
|
114
|
+
`lifecycle: ${routing.lifecycleState}`,
|
|
115
|
+
`capability: ${routing.capabilityState}`,
|
|
116
|
+
`routes: ${routing.routes.map(({ id }) => id).join(', ')}`,
|
|
117
|
+
...(routing.diagnostic ? [`diagnostic: ${routing.diagnostic}`] : []),
|
|
118
|
+
].join('\n'),
|
|
119
|
+
`contribution routing (${routing.surface})`,
|
|
120
|
+
);
|
|
121
|
+
p.outro('read-only routing report; no local or remote state changed');
|
|
122
|
+
} else {
|
|
123
|
+
const output = args.find((arg) => arg.startsWith('--output='))?.slice('--output='.length);
|
|
124
|
+
if (!output) throw new Error('contribute prepare requires --output=<path>');
|
|
125
|
+
const routing = await inspectContributionRouting({
|
|
126
|
+
consumerRoot, path, surface: 'guard',
|
|
127
|
+
});
|
|
128
|
+
if (!routing.routes.some(({ id }) => id === 'prepare-local')) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`local contribution preparation is unavailable: ${routing.capabilityState}` +
|
|
131
|
+
(routing.diagnostic ? ` (${routing.diagnostic})` : ''),
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
await prepareContributionArtifact({
|
|
135
|
+
kitRoot: KIT_ROOT, consumerRoot, path, output,
|
|
136
|
+
});
|
|
137
|
+
p.outro(`prepared local contribution artifact ${output}; no remote was changed`);
|
|
138
|
+
}
|
|
80
139
|
} else if (cmd === 'own' || cmd === 'disown') {
|
|
81
|
-
if (!args[1])
|
|
140
|
+
if (!args[1]) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`Usage: agent-workflow-kit ${cmd} <path>` +
|
|
143
|
+
(cmd === 'own' ? ' [--as=explicit-fork]' : ''),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
82
146
|
const origin = cmd === 'own' ? CONSUMER_ORIGIN : KIT_ORIGIN;
|
|
83
|
-
|
|
84
|
-
|
|
147
|
+
if (origin === CONSUMER_ORIGIN && ownershipState === 'contribution-bridge') {
|
|
148
|
+
await beginContributionBridge({ kitRoot: KIT_ROOT, consumerRoot, path: args[1] });
|
|
149
|
+
} else {
|
|
150
|
+
await setOwnership({ consumerRoot, path: args[1], origin, ownershipState });
|
|
151
|
+
}
|
|
152
|
+
p.outro(`${args[1]} is now ${origin}-owned` +
|
|
153
|
+
(origin === CONSUMER_ORIGIN ? ` (${ownershipState ?? 'explicit-fork'})` : ''));
|
|
85
154
|
} else {
|
|
86
|
-
p.note('Usage: agent-workflow-kit <init|update|diff|uninstall|own|disown>
|
|
155
|
+
p.note('Usage: agent-workflow-kit <init|update|diff|uninstall|own|disown|contribute> ' +
|
|
156
|
+
'[<path>] [--force] [--yes] [--owned] [--as=contribution-bridge|explicit-fork]');
|
|
87
157
|
p.outro('');
|
|
88
158
|
}
|
|
89
159
|
} catch (err) {
|
|
@@ -96,6 +166,7 @@ function printPlan(r) {
|
|
|
96
166
|
for (const k of [
|
|
97
167
|
'added', 'updated', 'userModified', 'consumerOwned', 'unchanged',
|
|
98
168
|
'deleted', 'keptDeleted', 'collisions', 'migrated',
|
|
169
|
+
'bridgeRetired',
|
|
99
170
|
])
|
|
100
171
|
if (r[k]?.length) lines.push(`${k}: ${r[k].length}`);
|
|
101
172
|
if (r.conflicts?.length) lines.push(`conflicts: ${r.conflicts.length}`);
|
|
@@ -119,18 +190,21 @@ function printPlan(r) {
|
|
|
119
190
|
p.note(lines.join('\n') || 'no changes', 'plan');
|
|
120
191
|
}
|
|
121
192
|
|
|
122
|
-
async function decideUpdate(action, path, yes) {
|
|
193
|
+
async function decideUpdate(action, path, yes, classification) {
|
|
194
|
+
if (yes) return nonInteractiveUpdateDecision(action);
|
|
123
195
|
if (action === 'delete') {
|
|
124
|
-
if (yes) return true;
|
|
125
196
|
return (await p.confirm({ message: `Upstream removed ${path} — delete it locally?` })) === true;
|
|
126
197
|
}
|
|
127
198
|
if (action === 'collision') {
|
|
128
|
-
if (yes) return 'replace';
|
|
129
199
|
const choice = await p.select({
|
|
130
|
-
message: `${path}
|
|
200
|
+
message: `${path} exists without ownership evidence. Choose an explicit route:`,
|
|
131
201
|
options: [
|
|
132
|
-
|
|
133
|
-
|
|
202
|
+
...(classification?.state === 'project-extension'
|
|
203
|
+
? [{ value: 'project-extension', label: 'Keep as declared Project extension' }]
|
|
204
|
+
: []),
|
|
205
|
+
{ value: 'contribution-bridge', label: 'Keep as temporary contribution bridge' },
|
|
206
|
+
{ value: 'explicit-fork', label: 'Keep as explicit fork' },
|
|
207
|
+
{ value: 'replace', label: 'Replace explicitly with Kit Core' },
|
|
134
208
|
],
|
|
135
209
|
});
|
|
136
210
|
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,16 @@ 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
|
+
if (origin === 'consumer' && ownershipState === 'contribution-bridge') {
|
|
10
|
+
throw new Error('contribution bridge requires `contribute start` with Kit provenance');
|
|
11
|
+
}
|
|
9
12
|
const manifestPath = join(consumerRoot, CONSUMER_MANIFEST_NAME);
|
|
10
13
|
const manifest = await readManifest(manifestPath);
|
|
11
14
|
if (!manifest) throw new Error('not initialised — run `init` first');
|
|
12
|
-
const next = withOrigin(manifest, path, origin);
|
|
15
|
+
const next = withOrigin(manifest, path, origin, ownershipState);
|
|
13
16
|
await validateConsumerFile(consumerRoot, path);
|
|
14
17
|
await writeManifest(manifestPath, next);
|
|
15
|
-
return { path, origin
|
|
18
|
+
return { path, origin, ownershipState: origin === 'consumer'
|
|
19
|
+
? (ownershipState ?? 'explicit-fork') : undefined };
|
|
16
20
|
}
|
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);
|
|
@@ -249,6 +260,7 @@ function verifyRelease(identities, kitVersion) {
|
|
|
249
260
|
function hasUpstreamDelta(result) {
|
|
250
261
|
return result.manifestChanged ||
|
|
251
262
|
(result.migrations?.length ?? 0) > 0 ||
|
|
263
|
+
(result.bridgeRetired?.length ?? 0) > 0 ||
|
|
252
264
|
result.added.length + result.updated.length + result.deleted.length > 0;
|
|
253
265
|
}
|
|
254
266
|
|
|
@@ -274,14 +286,68 @@ async function terminal(result, state, history, transition) {
|
|
|
274
286
|
conflicts: result.conflicts.map(({ path }) => path),
|
|
275
287
|
keptDeleted: result.keptDeleted,
|
|
276
288
|
},
|
|
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),
|
|
289
|
+
recommendation: updateRecommendation(result),
|
|
283
290
|
},
|
|
284
291
|
};
|
|
285
292
|
}
|
|
286
293
|
|
|
294
|
+
function updateRecommendation(result) {
|
|
295
|
+
if (result.migrationConflicts?.length) {
|
|
296
|
+
return migrationRecommendation(result.migrationConflicts);
|
|
297
|
+
}
|
|
298
|
+
if (result.collisions.length
|
|
299
|
+
|| result.conflicts.some(({ kind }) => kind === 'ownership-lifecycle')) {
|
|
300
|
+
return ownershipRecommendation(result);
|
|
301
|
+
}
|
|
302
|
+
return result.conflicts.length
|
|
303
|
+
? 'Review each named conflict; keep the local file or apply the incoming diff manually.'
|
|
304
|
+
: null;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function migrationConflictRecord(detail) {
|
|
308
|
+
const path = detail.split(': ', 1)[0];
|
|
309
|
+
const projectRegistry = path === PROJECT_SKILL_REGISTRY_PATH
|
|
310
|
+
|| path === '.claude/skills/skill-manifest.json';
|
|
311
|
+
return {
|
|
312
|
+
path,
|
|
313
|
+
kind: projectRegistry ? 'skill-registry' : 'prod-section',
|
|
314
|
+
diff: projectRegistry
|
|
315
|
+
? `Skill registry migration is ambiguous: ${detail}`
|
|
316
|
+
: 'Prod section differs from the other instruction surface or is malformed.',
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function migrationRecommendation(conflicts) {
|
|
321
|
+
const registryConflict = conflicts.some((detail) => (
|
|
322
|
+
detail.startsWith(`${PROJECT_SKILL_REGISTRY_PATH}:`)
|
|
323
|
+
|| detail.startsWith('.claude/skills/skill-manifest.json:')
|
|
324
|
+
));
|
|
325
|
+
return (registryConflict
|
|
326
|
+
? `Readiness migration is ambiguous in: ${conflicts.join(', ')}. `
|
|
327
|
+
: `Prod sections differ or are malformed in: ${conflicts.join(', ')}. `) +
|
|
328
|
+
'Resolve them manually; no consumer file was changed.';
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function ownershipRecommendation(result) {
|
|
332
|
+
const paths = new Set([
|
|
333
|
+
...result.collisions,
|
|
334
|
+
...result.conflicts.filter(({ kind }) => kind === 'ownership-lifecycle')
|
|
335
|
+
.map(({ path }) => path),
|
|
336
|
+
]);
|
|
337
|
+
const unresolved = (result.ownershipStates ?? []).filter(
|
|
338
|
+
({ path }) => paths.has(path),
|
|
339
|
+
);
|
|
340
|
+
return unresolved.map(({ path, state, evidence, routes }) => (
|
|
341
|
+
`${path}: ${state} ` +
|
|
342
|
+
`(package=${evidence.packageDeclared ? 'declared' : 'absent'}, ` +
|
|
343
|
+
`ledger=${evidence.ledgerOrigin}, destination=${evidence.destination}, ` +
|
|
344
|
+
`extension=${evidence.projectExtension}` +
|
|
345
|
+
(evidence.extensionDiagnostic
|
|
346
|
+
? `, extensionDiagnostic=${evidence.extensionDiagnostic}` : '') +
|
|
347
|
+
'); explicit routes: ' +
|
|
348
|
+
routes.map(({ id, action }) => `${id} (${action})`).join(', ')
|
|
349
|
+
)).join('\n') +
|
|
350
|
+
'. No consumer file was changed; --yes cannot choose a route.';
|
|
351
|
+
}
|
|
352
|
+
|
|
287
353
|
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
|