@1aboveio/skills 0.13.0 → 0.14.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/README.md +13 -11
- package/package.json +1 -1
- package/runtime/skills/distribution/generated/recipes.json +17 -17
- package/runtime/skills/distribution/scripts/bundles.mjs +113 -25
- package/runtime/skills/engineering/engineering-runtime/scripts/invocation-policy.mjs +187 -0
- package/runtime/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +19 -0
- package/runtime/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +9 -3
- package/skills/cicd-pipeline/mergify/references/watch-contract.md +7 -0
- package/skills/cicd-pipeline/mergify/scripts/watch-pr-delivery-core.mjs +60 -2
- package/skills/engineering/engineering-runtime/coherence/workflow.json +23 -16
- package/skills/engineering/engineering-runtime/scripts/invocation-policy.mjs +187 -0
- package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +19 -0
- package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +9 -3
- package/skills/engineering/resolve-issues/generated/workflow-repair-policy.json +20 -13
- package/skills/engineering/resolve-issues/scripts/run-state.mjs +14 -6
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ Run the reviewed shell prerequisites separately when you need their capabilities
|
|
|
24
24
|
#### Matt Pocock Toolkit
|
|
25
25
|
|
|
26
26
|
```sh
|
|
27
|
-
SKILLS_DOWNLOAD_MAX_BYTES=33554432 SKILLS_EXTRACT_MAX_FILES=4096 SKILLS_EXTRACT_MAX_BYTES=67108864 npx skills@1.5.22 add https://codeload.github.com/mattpocock/skills/tar.gz/ed37663cc5fbef691ddfecd080dff42f7e7e350d --global --agent universal claude-code --skill batch-grill-me code-review codebase-design diagnosing-bugs domain-modeling grill-me grill-with-docs grilling handoff improve-codebase-architecture prototype research resolving-merge-conflicts setup-matt-pocock-skills tdd to-spec to-tickets triage wayfinder
|
|
27
|
+
SKILLS_DOWNLOAD_MAX_BYTES=33554432 SKILLS_EXTRACT_MAX_FILES=4096 SKILLS_EXTRACT_MAX_BYTES=67108864 npx skills@1.5.22 add https://codeload.github.com/mattpocock/skills/tar.gz/ed37663cc5fbef691ddfecd080dff42f7e7e350d --global --agent universal claude-code --skill batch-grill-me code-review codebase-design diagnosing-bugs domain-modeling grill-me grill-with-docs grilling handoff improve-codebase-architecture prototype research resolving-merge-conflicts setup-matt-pocock-skills tdd to-spec to-tickets triage wayfinder --yes
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
#### Impeccable
|
|
@@ -51,15 +51,17 @@ Running `install` with no `--group` opens the interactive bundle picker; `update
|
|
|
51
51
|
- `--group <id>` — select one group; repeat the flag, or pass a comma-separated list, for several.
|
|
52
52
|
- `--all` — select every group in the catalog below. Mutually exclusive with `--group`.
|
|
53
53
|
- `--dry-run` — print the planned buckets and native commands, in order, and run none of them.
|
|
54
|
-
- `--yes` —
|
|
55
|
-
|
|
54
|
+
- `--yes` — accept every newly declared group member and use native symlink deployment without
|
|
55
|
+
coordinator prompts.
|
|
56
|
+
- `--enable-tdd` — keep TDD installed and allow implicit/model invocation (the default).
|
|
57
|
+
- `--disable-tdd` — keep TDD installed but allow only explicit user invocation.
|
|
56
58
|
|
|
57
59
|
Selecting several groups (interactive picker, repeated `--group`, or `--all`) installs their
|
|
58
60
|
members as one native `skills add` per skill from the unpacked package, so one skill's failure is
|
|
59
|
-
reported by name and never aborts the rest of the selection.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
reported by name and never aborts the rest of the selection. Every native add uses `--yes` because
|
|
62
|
+
`skills@1.5.22` has no independent `--symlink` flag: this selects the default canonical store plus
|
|
63
|
+
per-harness symlinks without an installation-method prompt, and also confirms replacement of each
|
|
64
|
+
planned same-named skill. The coordinator still owns group and new-member consent before planning.
|
|
63
65
|
|
|
64
66
|
## Groups in this package
|
|
65
67
|
|
|
@@ -82,7 +84,7 @@ is an error — the coordinator never infers groups from the filesystem — and
|
|
|
82
84
|
no longer selectable are dropped with a warning.
|
|
83
85
|
|
|
84
86
|
An accepted skill whose installed content already matches this package version's published digest is
|
|
85
|
-
skipped entirely: no native add
|
|
87
|
+
skipped entirely: no native add and no replacement. When everything remembered is unchanged and no
|
|
86
88
|
new skill is accepted, the run exits 0 as an explicit no-op. `--dry-run` prints the unchanged,
|
|
87
89
|
would-refresh, and would-add buckets without writing state or invoking the native CLI.
|
|
88
90
|
|
|
@@ -95,8 +97,8 @@ so the next update asks again.
|
|
|
95
97
|
|
|
96
98
|
Install and update are best-effort per skill: they continue after a single skill failure, summarize
|
|
97
99
|
every skill as succeeded, unchanged, declined, or failed, and exit non-zero if anything failed. There
|
|
98
|
-
is no rollback or transactional group update; the native CLI remains the sole write
|
|
99
|
-
|
|
100
|
+
is no rollback or transactional group update; the native CLI remains the sole filesystem write
|
|
101
|
+
authority. Selection memory follows the same per-skill rule: it is rewritten when
|
|
100
102
|
at least one user-facing member succeeded or was already unchanged, holding exactly those members —
|
|
101
103
|
failed and declined members are omitted — and a run with no user-facing success leaves the prior
|
|
102
104
|
file untouched.
|
|
@@ -127,7 +129,7 @@ Removing a group leaves its dependency groups and every unrelated skill installe
|
|
|
127
129
|
## Lifecycle
|
|
128
130
|
|
|
129
131
|
`skills@1.5.22` is the only lifecycle authority for this package. The package
|
|
130
|
-
plans first-party commands; the native CLI owns installation, linking,
|
|
132
|
+
plans first-party commands; the native CLI owns installation, linking, replacement, update
|
|
131
133
|
and removal. External prerequisites have separate upstream lifecycle and are never package steps.
|
|
132
134
|
|
|
133
135
|
## Licensing
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"id": "first-party",
|
|
9
9
|
"type": "first-party",
|
|
10
10
|
"package": "@1aboveio/skills",
|
|
11
|
-
"version": "0.
|
|
11
|
+
"version": "0.14.0",
|
|
12
12
|
"updatePolicy": "pinned-npm-version",
|
|
13
13
|
"members": [
|
|
14
14
|
{
|
|
@@ -184,7 +184,7 @@
|
|
|
184
184
|
"sourceId": "first-party",
|
|
185
185
|
"sourceType": "first-party",
|
|
186
186
|
"package": "@1aboveio/skills",
|
|
187
|
-
"version": "0.
|
|
187
|
+
"version": "0.14.0",
|
|
188
188
|
"installPath": null,
|
|
189
189
|
"members": [
|
|
190
190
|
"harness-runtime",
|
|
@@ -200,7 +200,7 @@
|
|
|
200
200
|
"commands": [
|
|
201
201
|
{
|
|
202
202
|
"transport": "local",
|
|
203
|
-
"command": "npx skills@1.5.22 add . --global --agent universal claude-code --skill harness-runtime e2e-test ensure-coverage implement-and-pr resolve-issues resolve-release review-pr smoke engineering-runtime && npm
|
|
203
|
+
"command": "npx skills@1.5.22 add . --global --agent universal claude-code --skill harness-runtime e2e-test ensure-coverage implement-and-pr resolve-issues resolve-release review-pr smoke engineering-runtime && npm --prefix \"$(realpath \"$HOME/.agents/skills/engineering-runtime/scripts\")\" ci --ignore-scripts"
|
|
204
204
|
}
|
|
205
205
|
],
|
|
206
206
|
"onFailure": {
|
|
@@ -218,7 +218,7 @@
|
|
|
218
218
|
"sourceId": "first-party",
|
|
219
219
|
"sourceType": "first-party",
|
|
220
220
|
"package": "@1aboveio/skills",
|
|
221
|
-
"version": "0.
|
|
221
|
+
"version": "0.14.0",
|
|
222
222
|
"installPath": null,
|
|
223
223
|
"members": [
|
|
224
224
|
"harness-runtime",
|
|
@@ -234,7 +234,7 @@
|
|
|
234
234
|
"commands": [
|
|
235
235
|
{
|
|
236
236
|
"transport": "local",
|
|
237
|
-
"command": "npx skills@1.5.22 add . --global --agent universal claude-code --skill harness-runtime e2e-test ensure-coverage implement-and-pr resolve-issues resolve-release review-pr smoke engineering-runtime && npm
|
|
237
|
+
"command": "npx skills@1.5.22 add . --global --agent universal claude-code --skill harness-runtime e2e-test ensure-coverage implement-and-pr resolve-issues resolve-release review-pr smoke engineering-runtime && npm --prefix \"$(realpath \"$HOME/.agents/skills/engineering-runtime/scripts\")\" ci --ignore-scripts"
|
|
238
238
|
}
|
|
239
239
|
],
|
|
240
240
|
"onFailure": {
|
|
@@ -262,7 +262,7 @@
|
|
|
262
262
|
"sourceId": "first-party",
|
|
263
263
|
"sourceType": "first-party",
|
|
264
264
|
"package": "@1aboveio/skills",
|
|
265
|
-
"version": "0.
|
|
265
|
+
"version": "0.14.0",
|
|
266
266
|
"installPath": null,
|
|
267
267
|
"members": [
|
|
268
268
|
"harness-runtime",
|
|
@@ -295,7 +295,7 @@
|
|
|
295
295
|
"sourceId": "first-party",
|
|
296
296
|
"sourceType": "first-party",
|
|
297
297
|
"package": "@1aboveio/skills",
|
|
298
|
-
"version": "0.
|
|
298
|
+
"version": "0.14.0",
|
|
299
299
|
"installPath": null,
|
|
300
300
|
"members": [
|
|
301
301
|
"harness-runtime",
|
|
@@ -338,7 +338,7 @@
|
|
|
338
338
|
"sourceId": "first-party",
|
|
339
339
|
"sourceType": "first-party",
|
|
340
340
|
"package": "@1aboveio/skills",
|
|
341
|
-
"version": "0.
|
|
341
|
+
"version": "0.14.0",
|
|
342
342
|
"installPath": null,
|
|
343
343
|
"members": [
|
|
344
344
|
"harness-runtime",
|
|
@@ -368,7 +368,7 @@
|
|
|
368
368
|
"sourceId": "first-party",
|
|
369
369
|
"sourceType": "first-party",
|
|
370
370
|
"package": "@1aboveio/skills",
|
|
371
|
-
"version": "0.
|
|
371
|
+
"version": "0.14.0",
|
|
372
372
|
"installPath": null,
|
|
373
373
|
"members": [
|
|
374
374
|
"harness-runtime",
|
|
@@ -408,7 +408,7 @@
|
|
|
408
408
|
"sourceId": "first-party",
|
|
409
409
|
"sourceType": "first-party",
|
|
410
410
|
"package": "@1aboveio/skills",
|
|
411
|
-
"version": "0.
|
|
411
|
+
"version": "0.14.0",
|
|
412
412
|
"installPath": null,
|
|
413
413
|
"members": [
|
|
414
414
|
"harness-runtime",
|
|
@@ -440,7 +440,7 @@
|
|
|
440
440
|
"sourceId": "first-party",
|
|
441
441
|
"sourceType": "first-party",
|
|
442
442
|
"package": "@1aboveio/skills",
|
|
443
|
-
"version": "0.
|
|
443
|
+
"version": "0.14.0",
|
|
444
444
|
"installPath": null,
|
|
445
445
|
"members": [
|
|
446
446
|
"harness-runtime",
|
|
@@ -480,7 +480,7 @@
|
|
|
480
480
|
"sourceId": "first-party",
|
|
481
481
|
"sourceType": "first-party",
|
|
482
482
|
"package": "@1aboveio/skills",
|
|
483
|
-
"version": "0.
|
|
483
|
+
"version": "0.14.0",
|
|
484
484
|
"installPath": null,
|
|
485
485
|
"members": [
|
|
486
486
|
"harness-runtime"
|
|
@@ -506,7 +506,7 @@
|
|
|
506
506
|
"sourceId": "first-party",
|
|
507
507
|
"sourceType": "first-party",
|
|
508
508
|
"package": "@1aboveio/skills",
|
|
509
|
-
"version": "0.
|
|
509
|
+
"version": "0.14.0",
|
|
510
510
|
"installPath": null,
|
|
511
511
|
"members": [
|
|
512
512
|
"harness-runtime"
|
|
@@ -538,7 +538,7 @@
|
|
|
538
538
|
"sourceId": "first-party",
|
|
539
539
|
"sourceType": "first-party",
|
|
540
540
|
"package": "@1aboveio/skills",
|
|
541
|
-
"version": "0.
|
|
541
|
+
"version": "0.14.0",
|
|
542
542
|
"installPath": null,
|
|
543
543
|
"members": [
|
|
544
544
|
"harness-runtime",
|
|
@@ -571,7 +571,7 @@
|
|
|
571
571
|
"commands": [
|
|
572
572
|
{
|
|
573
573
|
"transport": "local",
|
|
574
|
-
"command": "npx skills@1.5.22 add . --global --agent universal claude-code --skill harness-runtime e2e-test ensure-coverage implement-and-pr resolve-issues resolve-release review-pr smoke engineering-runtime cloud-build cloud-debug cloud-deploy docker google-cloud mergify podman airflow-dag-develop app-debug pyspark python-backend better-auth monorepo nextjs-fullstack prisma-setup shadcn zod-v4 && npm
|
|
574
|
+
"command": "npx skills@1.5.22 add . --global --agent universal claude-code --skill harness-runtime e2e-test ensure-coverage implement-and-pr resolve-issues resolve-release review-pr smoke engineering-runtime cloud-build cloud-debug cloud-deploy docker google-cloud mergify podman airflow-dag-develop app-debug pyspark python-backend better-auth monorepo nextjs-fullstack prisma-setup shadcn zod-v4 && npm --prefix \"$(realpath \"$HOME/.agents/skills/engineering-runtime/scripts\")\" ci --ignore-scripts"
|
|
575
575
|
}
|
|
576
576
|
],
|
|
577
577
|
"onFailure": {
|
|
@@ -589,7 +589,7 @@
|
|
|
589
589
|
"sourceId": "first-party",
|
|
590
590
|
"sourceType": "first-party",
|
|
591
591
|
"package": "@1aboveio/skills",
|
|
592
|
-
"version": "0.
|
|
592
|
+
"version": "0.14.0",
|
|
593
593
|
"installPath": null,
|
|
594
594
|
"members": [
|
|
595
595
|
"harness-runtime",
|
|
@@ -622,7 +622,7 @@
|
|
|
622
622
|
"commands": [
|
|
623
623
|
{
|
|
624
624
|
"transport": "local",
|
|
625
|
-
"command": "npx skills@1.5.22 add . --global --agent universal claude-code --skill harness-runtime e2e-test ensure-coverage implement-and-pr resolve-issues resolve-release review-pr smoke engineering-runtime cloud-build cloud-debug cloud-deploy docker google-cloud mergify podman airflow-dag-develop app-debug pyspark python-backend better-auth monorepo nextjs-fullstack prisma-setup shadcn zod-v4 && npm
|
|
625
|
+
"command": "npx skills@1.5.22 add . --global --agent universal claude-code --skill harness-runtime e2e-test ensure-coverage implement-and-pr resolve-issues resolve-release review-pr smoke engineering-runtime cloud-build cloud-debug cloud-deploy docker google-cloud mergify podman airflow-dag-develop app-debug pyspark python-backend better-auth monorepo nextjs-fullstack prisma-setup shadcn zod-v4 && npm --prefix \"$(realpath \"$HOME/.agents/skills/engineering-runtime/scripts\")\" ci --ignore-scripts"
|
|
626
626
|
}
|
|
627
627
|
],
|
|
628
628
|
"onFailure": {
|
|
@@ -7,6 +7,10 @@ import { emitKeypressEvents } from 'node:readline';
|
|
|
7
7
|
import { dirname, join, resolve } from 'node:path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
|
|
10
|
+
import {
|
|
11
|
+
applyInvocationPolicy,
|
|
12
|
+
reviewedSourceInjections,
|
|
13
|
+
} from '../../engineering/engineering-runtime/scripts/invocation-policy.mjs';
|
|
10
14
|
import { isMainModule } from '../../engineering/engineering-runtime/scripts/main-module.mjs';
|
|
11
15
|
// The digest authority for the update skip is the ONE existing predicate pair: contentDigest with
|
|
12
16
|
// the published-content scope for first-party members, exact-tree for external ones — the same
|
|
@@ -80,6 +84,9 @@ export function loadSelectionState(env = process.env) {
|
|
|
80
84
|
if (!Array.isArray(parsed.groups)) {
|
|
81
85
|
throw selectionError(path, 'is invalid: groups must be an array');
|
|
82
86
|
}
|
|
87
|
+
if (parsed.tddImplicitInvocation !== undefined && typeof parsed.tddImplicitInvocation !== 'boolean') {
|
|
88
|
+
throw selectionError(path, 'is invalid: tddImplicitInvocation must be a boolean');
|
|
89
|
+
}
|
|
83
90
|
for (const group of parsed.groups) {
|
|
84
91
|
const valid = group
|
|
85
92
|
&& typeof group === 'object'
|
|
@@ -132,7 +139,14 @@ function rememberedGroupIds(state, catalog, stderr) {
|
|
|
132
139
|
// declined is dropped entirely, so the next no-arg update never replays a group this run did not
|
|
133
140
|
// actually deliver. The transport label is observational only — both render targets share this
|
|
134
141
|
// file.
|
|
135
|
-
export function selectionForRun(action, groupIds, {
|
|
142
|
+
export function selectionForRun(action, groupIds, {
|
|
143
|
+
renderTarget,
|
|
144
|
+
now = new Date(),
|
|
145
|
+
succeededMembers,
|
|
146
|
+
omitMembers,
|
|
147
|
+
tddImplicitInvocation = true,
|
|
148
|
+
...transport
|
|
149
|
+
} = {}) {
|
|
136
150
|
const omitted = new Set(omitMembers ?? []);
|
|
137
151
|
const recipes = readRecipes(resolveBundleTransport(transport).recipesPath);
|
|
138
152
|
const groups = new Map(recipes.groups.map((group) => [group.id, group]));
|
|
@@ -141,6 +155,7 @@ export function selectionForRun(action, groupIds, { renderTarget, now = new Date
|
|
|
141
155
|
schemaVersion: SELECTION_SCHEMA_VERSION,
|
|
142
156
|
updatedAt: now.toISOString(),
|
|
143
157
|
transport: { renderTarget },
|
|
158
|
+
tddImplicitInvocation,
|
|
144
159
|
groups: groupIds
|
|
145
160
|
.map((groupId) => {
|
|
146
161
|
const group = groups.get(groupId);
|
|
@@ -274,9 +289,9 @@ function localFirstPartyCommand(command, localRoot, groupId) {
|
|
|
274
289
|
// adds let the run record exactly which skill failed, continue with the remaining skills, and keep
|
|
275
290
|
// only the succeeded skills in selection memory. Recipe step/member order is preserved
|
|
276
291
|
// (dependency-first), and a member shared by several selected groups (harness-runtime) is still
|
|
277
|
-
// planned exactly once, at its first occurrence.
|
|
278
|
-
//
|
|
279
|
-
//
|
|
292
|
+
// planned exactly once, at its first occurrence. The pinned native CLI has no `--symlink` selector,
|
|
293
|
+
// so the coordinator adds `--yes` to choose its default symlink deployment without one installation-
|
|
294
|
+
// method prompt per skill. Attribution, ordering and deduplication remain coordinator-owned.
|
|
280
295
|
const SKILL_TAIL = /^(.*? --skill )(.+?)( && .*)?$/;
|
|
281
296
|
|
|
282
297
|
// The generated step binds its runtime suffix (` && npm ci ...`) to the whole coalesced command.
|
|
@@ -303,7 +318,12 @@ function withYesFlag(command) {
|
|
|
303
318
|
return `${match[1]}${match[2]} --yes${match[3] || ''}`;
|
|
304
319
|
}
|
|
305
320
|
|
|
306
|
-
export function planBundleCommands(action, groupIds, {
|
|
321
|
+
export function planBundleCommands(action, groupIds, {
|
|
322
|
+
enableTdd = true,
|
|
323
|
+
skipMembers,
|
|
324
|
+
omitMembers,
|
|
325
|
+
...transport
|
|
326
|
+
} = {}) {
|
|
307
327
|
if (!ACTIONS.has(action)) throw new Error(`Unsupported bundle action: ${action}`);
|
|
308
328
|
// Members the user declined this run (#1128) are filtered out of every native --skill list.
|
|
309
329
|
// Support dependencies (harness-runtime) are never omittable: they are silent install
|
|
@@ -352,13 +372,19 @@ export function planBundleCommands(action, groupIds, { yes = false, skipMembers,
|
|
|
352
372
|
}
|
|
353
373
|
let skillCommand = withSkills(bare, [skill]);
|
|
354
374
|
if (suffix && skill === RUNTIME_MEMBER) skillCommand = `${skillCommand}${suffix}`;
|
|
355
|
-
|
|
375
|
+
// The pinned CLI has no --symlink flag. --yes is its only supported way to select the
|
|
376
|
+
// default symlink deployment without presenting the install-method chooser.
|
|
377
|
+
skillCommand = withYesFlag(skillCommand);
|
|
378
|
+
const invocationPolicy = skill === 'tdd' && enableTdd
|
|
379
|
+
? undefined
|
|
380
|
+
: step.invocationPolicies?.find((candidate) => candidate.installName === skill)?.policy;
|
|
356
381
|
const entry = {
|
|
357
382
|
member: skill,
|
|
358
383
|
groupIds: [groupId],
|
|
359
384
|
groupId,
|
|
360
385
|
groupName: group.displayName,
|
|
361
386
|
sourceId: step.sourceId,
|
|
387
|
+
...(invocationPolicy ? { invocationPolicy } : {}),
|
|
362
388
|
command: skillCommand,
|
|
363
389
|
cwd: localRoot,
|
|
364
390
|
failureMessage: step.onFailure.message,
|
|
@@ -378,6 +404,8 @@ export function planBundleCommands(action, groupIds, { yes = false, skipMembers,
|
|
|
378
404
|
// exit status, and selection persistence sees exactly which members succeeded.
|
|
379
405
|
export function runBundleCommands(commands, {
|
|
380
406
|
execute = (command, cwd) => spawnSync('/bin/bash', ['-lc', command], { cwd, stdio: 'inherit' }),
|
|
407
|
+
applyPolicy = applyInvocationPolicy,
|
|
408
|
+
skillsRoot,
|
|
381
409
|
stdout = process.stdout,
|
|
382
410
|
stderr = process.stderr,
|
|
383
411
|
} = {}) {
|
|
@@ -387,8 +415,21 @@ export function runBundleCommands(commands, {
|
|
|
387
415
|
stdout.write(`\nInstalling ${planned.member} (${planned.sourceId})...\n`);
|
|
388
416
|
const result = execute(planned.command, planned.cwd);
|
|
389
417
|
if (result.status === 0) {
|
|
390
|
-
|
|
391
|
-
|
|
418
|
+
try {
|
|
419
|
+
if (planned.invocationPolicy) {
|
|
420
|
+
applyPolicy({
|
|
421
|
+
installName: planned.member,
|
|
422
|
+
policy: planned.invocationPolicy,
|
|
423
|
+
...(skillsRoot ? { skillsRoot } : {}),
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
succeeded.push(planned);
|
|
427
|
+
continue;
|
|
428
|
+
} catch (error) {
|
|
429
|
+
stderr.write(`Invocation policy failed for ${planned.member}: ${error.message}\n`);
|
|
430
|
+
failed.push({ planned, status: 1 });
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
392
433
|
}
|
|
393
434
|
stderr.write(`${planned.failureMessage}\n`);
|
|
394
435
|
failed.push({ planned, status: Number.isInteger(result.status) ? result.status : 1 });
|
|
@@ -414,10 +455,9 @@ function printRunSummary(stdout, { succeeded, failed, unchanged = [], declined =
|
|
|
414
455
|
// --- Digest classification (epic #1124 / issue #1127) --------------------------------------------
|
|
415
456
|
//
|
|
416
457
|
// An `update` that re-adds a skill whose installed content already digests to what this invocation
|
|
417
|
-
// would install buys nothing and
|
|
418
|
-
//
|
|
419
|
-
//
|
|
420
|
-
// the coordinator may not copy, link, or overwrite anything itself.
|
|
458
|
+
// would install buys nothing and replaces identical bytes. So update classifies every accepted
|
|
459
|
+
// member first and omits the digest-equal ones from the native add. The native CLI remains the sole
|
|
460
|
+
// write authority; the coordinator may not copy, link, or overwrite skill trees itself.
|
|
421
461
|
//
|
|
422
462
|
// The digest semantics are borrowed, never re-invented: first-party members digest their PUBLISHED
|
|
423
463
|
// content (`published: true`) on both transports, external members digest the exact reviewed tree,
|
|
@@ -445,14 +485,25 @@ export function expectedMemberDigest(source, member, { localRoot }) {
|
|
|
445
485
|
|
|
446
486
|
// Classify one installed member directory against its expectation. `skillsRoot` is the canonical
|
|
447
487
|
// installed store (`~/.agents/skills`); the member lives under it by installName.
|
|
448
|
-
export function classifyInstalledMember(source, member, { localRoot, skillsRoot }) {
|
|
488
|
+
export function classifyInstalledMember(source, member, { enableTdd = true, localRoot, skillsRoot }) {
|
|
449
489
|
const directory = join(skillsRoot, member.installName);
|
|
450
490
|
const entry = { member: member.installName, sourceId: source?.id ?? null, directory };
|
|
451
491
|
if (!statSync(directory, { throwIfNoEntry: false })?.isDirectory()) {
|
|
452
492
|
return { ...entry, status: 'missing' };
|
|
453
493
|
}
|
|
454
494
|
const expectedDigest = expectedMemberDigest(source, member, { localRoot });
|
|
455
|
-
|
|
495
|
+
let actualDigest;
|
|
496
|
+
try {
|
|
497
|
+
const policyInjections = member.installName === 'tdd' && member.invocationPolicy && !enableTdd
|
|
498
|
+
? reviewedSourceInjections(directory, member.invocationPolicy)
|
|
499
|
+
: [];
|
|
500
|
+
actualDigest = contentDigest(directory, {
|
|
501
|
+
injected: policyInjections,
|
|
502
|
+
published: firstPartySource(source),
|
|
503
|
+
});
|
|
504
|
+
} catch {
|
|
505
|
+
return { ...entry, status: 'refresh', expectedDigest, actualDigest: null };
|
|
506
|
+
}
|
|
456
507
|
return {
|
|
457
508
|
...entry,
|
|
458
509
|
status: classifyMember({ installed: true, expectedDigest, actualDigest }),
|
|
@@ -465,7 +516,7 @@ export function classifyInstalledMember(source, member, { localRoot, skillsRoot
|
|
|
465
516
|
// deduplicated across groups (the shared support runtime classifies once). The buckets drive both
|
|
466
517
|
// the planner's skip set and the dry-run/no-op rendering. A member the recipe names but its source
|
|
467
518
|
// does not declare gets no expected digest — refresh-or-missing, never a silent skip.
|
|
468
|
-
export function classifySelectedMembers(action, groupIds, { skillsRoot, ...transport } = {}) {
|
|
519
|
+
export function classifySelectedMembers(action, groupIds, { enableTdd = true, skillsRoot, ...transport } = {}) {
|
|
469
520
|
if (!ACTIONS.has(action)) throw new Error(`Unsupported bundle action: ${action}`);
|
|
470
521
|
const { recipesPath, localRoot } = resolveBundleTransport(transport);
|
|
471
522
|
const recipes = readRecipes(recipesPath);
|
|
@@ -484,7 +535,7 @@ export function classifySelectedMembers(action, groupIds, { skillsRoot, ...trans
|
|
|
484
535
|
if (seen.has(installName)) continue;
|
|
485
536
|
seen.add(installName);
|
|
486
537
|
const member = membersByName.get(installName) ?? { installName };
|
|
487
|
-
const entry = classifyInstalledMember(source, member, { localRoot, skillsRoot });
|
|
538
|
+
const entry = classifyInstalledMember(source, member, { enableTdd, localRoot, skillsRoot });
|
|
488
539
|
buckets[entry.status].push(entry);
|
|
489
540
|
}
|
|
490
541
|
}
|
|
@@ -510,19 +561,21 @@ function printUpdateBuckets(stdout, buckets) {
|
|
|
510
561
|
function usage() {
|
|
511
562
|
return `Usage:
|
|
512
563
|
node skills/distribution/scripts/bundles.mjs list [--recipes <path>]
|
|
513
|
-
node skills/distribution/scripts/bundles.mjs <install|update> [--group <id> ... | --all] [--dry-run] [--yes] [--recipes <path>] [--local-root <path>]
|
|
564
|
+
node skills/distribution/scripts/bundles.mjs <install|update> [--group <id> ... | --all] [--enable-tdd|--disable-tdd] [--dry-run] [--yes] [--recipes <path>] [--local-root <path>]
|
|
514
565
|
|
|
515
566
|
install without --group/--all opens the interactive picker; update without them replays the
|
|
516
567
|
remembered selection (\${XDG_STATE_HOME:-~/.local/state}/1aboveio-skills/selection.json).
|
|
517
568
|
When a remembered group's current recipe declares members you have not accepted yet, each new skill
|
|
518
569
|
is offered individually and defaults to No; --yes accepts them all, --dry-run lists them without
|
|
519
|
-
prompting.
|
|
570
|
+
prompting. TDD implicit model invocation is enabled by default; --disable-tdd makes TDD explicit-only,
|
|
571
|
+
and --enable-tdd restores implicit invocation. --manual-tdd remains a compatibility alias for
|
|
572
|
+
--disable-tdd.
|
|
520
573
|
`;
|
|
521
574
|
}
|
|
522
575
|
|
|
523
576
|
function parseArgs(args) {
|
|
524
577
|
const [action = 'install', ...options] = args;
|
|
525
|
-
const parsed = { action, groupIds: [], all: false, dryRun: false, yes: false };
|
|
578
|
+
const parsed = { action, groupIds: [], all: false, dryRun: false, yes: false, tddMode: null };
|
|
526
579
|
for (let index = 0; index < options.length; index += 1) {
|
|
527
580
|
const option = options[index];
|
|
528
581
|
if (option === '--group') {
|
|
@@ -538,9 +591,15 @@ function parseArgs(args) {
|
|
|
538
591
|
} else if (option === '--all') parsed.all = true;
|
|
539
592
|
else if (option === '--dry-run') parsed.dryRun = true;
|
|
540
593
|
else if (option === '--yes') parsed.yes = true;
|
|
594
|
+
else if (option === '--enable-tdd') parsed.tddMode = 'implicit';
|
|
595
|
+
else if (option === '--disable-tdd' || option === '--manual-tdd') parsed.tddMode = 'manual-only';
|
|
541
596
|
else throw new Error(`Unknown option: ${option}`);
|
|
542
597
|
}
|
|
543
598
|
if (parsed.all && parsed.groupIds.length > 0) throw new Error('Use either --all or --group, not both');
|
|
599
|
+
const tddOptions = options.filter((option) => ['--enable-tdd', '--disable-tdd', '--manual-tdd'].includes(option));
|
|
600
|
+
if (tddOptions.length > 1) {
|
|
601
|
+
throw new Error('Use only one of --enable-tdd or --disable-tdd');
|
|
602
|
+
}
|
|
544
603
|
return parsed;
|
|
545
604
|
}
|
|
546
605
|
|
|
@@ -683,6 +742,11 @@ async function consentNewMembers(candidates, { yes, stdin, stdout, stderr }) {
|
|
|
683
742
|
return declined;
|
|
684
743
|
}
|
|
685
744
|
|
|
745
|
+
function selectionIncludesTdd(groupIds, catalog) {
|
|
746
|
+
const selected = new Set(groupIds);
|
|
747
|
+
return catalog.some((bundle) => selected.has(bundle.id) && bundle.members.includes('tdd'));
|
|
748
|
+
}
|
|
749
|
+
|
|
686
750
|
// Dependencies are injectable so the coordinator seam can be tested black-box: a stubbed native
|
|
687
751
|
// executor, controllable TTY streams, and a fixture XDG environment, without reaching process
|
|
688
752
|
// globals. The default execute is the real shell out to the native Skills CLI — the sole
|
|
@@ -690,6 +754,7 @@ async function consentNewMembers(candidates, { yes, stdin, stdout, stderr }) {
|
|
|
690
754
|
// AFTER those native commands settle.
|
|
691
755
|
export async function runCli(args, {
|
|
692
756
|
execute,
|
|
757
|
+
applyPolicy,
|
|
693
758
|
stdin = process.stdin,
|
|
694
759
|
stdout = process.stdout,
|
|
695
760
|
stderr = process.stderr,
|
|
@@ -726,6 +791,17 @@ export async function runCli(args, {
|
|
|
726
791
|
priorState = loadPriorSelection(env);
|
|
727
792
|
}
|
|
728
793
|
if (groupIds.length === 0) throw new Error('Select at least one bundle');
|
|
794
|
+
const includesTdd = selectionIncludesTdd(groupIds, catalog);
|
|
795
|
+
if (parsed.tddMode && !includesTdd) {
|
|
796
|
+
throw new Error(`${parsed.tddMode === 'implicit' ? '--enable-tdd' : '--disable-tdd'} requires a selected bundle containing tdd`);
|
|
797
|
+
}
|
|
798
|
+
let enableTdd = typeof priorState?.tddImplicitInvocation === 'boolean'
|
|
799
|
+
? priorState.tddImplicitInvocation
|
|
800
|
+
: true;
|
|
801
|
+
if (includesTdd) {
|
|
802
|
+
if (parsed.tddMode) enableTdd = parsed.tddMode === 'implicit';
|
|
803
|
+
stdout.write(`TDD invocation: ${enableTdd ? 'enabled' : 'disabled'}\n`);
|
|
804
|
+
}
|
|
729
805
|
// New-member consent (#1128): newly declared user-facing members of a group with prior
|
|
730
806
|
// acceptance are offered one at a time, defaulting to No. A dry run only LISTS the would-be
|
|
731
807
|
// candidates — it never prompts and never writes state.
|
|
@@ -740,13 +816,13 @@ export async function runCli(args, {
|
|
|
740
816
|
}
|
|
741
817
|
// Digest skip (#1127): `update` classifies the accepted members against the installed tree and
|
|
742
818
|
// omits the digest-equal ones from the native add. `install` never skips — it is an explicit
|
|
743
|
-
// request to install
|
|
744
|
-
//
|
|
819
|
+
// request to install/replace the selected members. Declined new members (#1128) are excluded
|
|
820
|
+
// from classification: they never run, so they are not buckets.
|
|
745
821
|
let buckets;
|
|
746
822
|
let skipMembers;
|
|
823
|
+
const skillsRoot = join(env.HOME || homedir(), '.agents', 'skills');
|
|
747
824
|
if (parsed.action === 'update') {
|
|
748
|
-
|
|
749
|
-
buckets = classifySelectedMembers(parsed.action, groupIds, { ...transport, skillsRoot });
|
|
825
|
+
buckets = classifySelectedMembers(parsed.action, groupIds, { ...transport, enableTdd, skillsRoot });
|
|
750
826
|
skipMembers = new Set(buckets.unchanged.map((entry) => entry.member));
|
|
751
827
|
if (declined.size > 0) {
|
|
752
828
|
for (const key of Object.keys(buckets)) {
|
|
@@ -754,7 +830,12 @@ export async function runCli(args, {
|
|
|
754
830
|
}
|
|
755
831
|
}
|
|
756
832
|
}
|
|
757
|
-
const commands = planBundleCommands(parsed.action, groupIds, {
|
|
833
|
+
const commands = planBundleCommands(parsed.action, groupIds, {
|
|
834
|
+
...transport,
|
|
835
|
+
enableTdd,
|
|
836
|
+
skipMembers,
|
|
837
|
+
omitMembers: declined,
|
|
838
|
+
});
|
|
758
839
|
if (parsed.dryRun) {
|
|
759
840
|
if (buckets) printUpdateBuckets(stdout, buckets);
|
|
760
841
|
for (const planned of commands) {
|
|
@@ -769,7 +850,13 @@ export async function runCli(args, {
|
|
|
769
850
|
const skipping = buckets.unchanged.map((entry) => entry.member).filter((member) => !SUPPORT_GROUPS.has(member));
|
|
770
851
|
if (skipping.length > 0) stdout.write(`Up to date, skipping: ${skipping.join(', ')}\n`);
|
|
771
852
|
}
|
|
772
|
-
const { succeeded, failed } = runBundleCommands(commands, {
|
|
853
|
+
const { succeeded, failed } = runBundleCommands(commands, {
|
|
854
|
+
execute,
|
|
855
|
+
applyPolicy,
|
|
856
|
+
skillsRoot,
|
|
857
|
+
stdout,
|
|
858
|
+
stderr,
|
|
859
|
+
});
|
|
773
860
|
printRunSummary(stdout, {
|
|
774
861
|
succeeded,
|
|
775
862
|
failed,
|
|
@@ -791,6 +878,7 @@ export async function runCli(args, {
|
|
|
791
878
|
renderTarget,
|
|
792
879
|
succeededMembers,
|
|
793
880
|
omitMembers: declined,
|
|
881
|
+
tddImplicitInvocation: enableTdd,
|
|
794
882
|
});
|
|
795
883
|
if (selection.groups.length > 0) {
|
|
796
884
|
writeSelectionState(selection, env);
|