@ikon85/agent-workflow-kit 0.43.0 → 0.44.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/grill-me/SKILL.md +3 -1
- package/.agents/skills/grill-with-docs/SKILL.md +3 -1
- package/.agents/skills/kit-update/SKILL.md +9 -11
- package/.agents/skills/orchestrate-wave/SKILL.md +8 -8
- package/.agents/skills/setup-workflow/SKILL.md +20 -18
- package/.agents/skills/setup-workflow/board-sync.md +4 -1
- package/.agents/skills/setup-workflow/workflow-advisories.md +3 -2
- package/.agents/skills/setup-workflow/worktree-lifecycle.md +36 -31
- package/.agents/skills/to-issues/SKILL.md +43 -0
- package/.agents/skills/wrapup/SKILL.md +98 -54
- package/.claude/skills/grill-me/SKILL.md +3 -1
- package/.claude/skills/grill-me-codex/SKILL.md +3 -1
- package/.claude/skills/grill-with-docs/SKILL.md +3 -1
- package/.claude/skills/grill-with-docs-codex/SKILL.md +3 -1
- package/.claude/skills/kit-update/SKILL.md +9 -11
- package/.claude/skills/orchestrate-wave/SKILL.md +8 -8
- package/.claude/skills/setup-workflow/SKILL.md +20 -18
- package/.claude/skills/setup-workflow/board-sync.md +4 -1
- package/.claude/skills/setup-workflow/workflow-advisories.md +3 -2
- package/.claude/skills/setup-workflow/worktree-lifecycle.md +36 -31
- package/.claude/skills/to-issues/SKILL.md +43 -0
- package/.claude/skills/wrapup/SKILL.md +98 -54
- package/README.md +249 -7
- package/agent-workflow-kit.package.json +42 -42
- package/package.json +16 -12
- package/scripts/marker_lib.py +3 -0
- package/scripts/profile_globs.py +26 -45
- package/scripts/release-delta-guard.mjs +26 -9
- package/scripts/worktree-lifecycle/README.md +57 -160
- package/scripts/worktree-lifecycle/capabilities.json +2 -3
- package/scripts/worktree-lifecycle/classify.py +480 -0
- package/scripts/worktree-lifecycle/cleanup.py +9 -29
- package/scripts/worktree-lifecycle/core.py +68 -1429
- package/scripts/worktree-lifecycle/ignore_seed.py +1 -1
- package/scripts/worktree-lifecycle/profile.py +46 -29
- package/scripts/worktree-lifecycle/setup.py +7 -19
- package/scripts/wrapup-land.py +1292 -545
- package/src/consumer-migrations.json +1 -16
- package/src/lib/bundle.mjs +31 -1
- package/docs/adr/0001-consumer-divergence-policy.md +0 -53
- package/docs/adr/0002-capability-gated-orchestration.md +0 -70
- package/docs/adr/0003-kit-core-and-project-extension-lifecycle.md +0 -63
- package/docs/adr/0004-release-intent-is-a-version-tag.md +0 -64
- package/docs/adr/0005-to-issues-is-the-planning-facade.md +0 -42
- package/docs/adr/0006-routing-knowledge-access-and-policy-are-separate.md +0 -95
- package/docs/adr/0007-session-teardown-requires-provenance-bound-ownership.md +0 -88
- package/docs/adr/0008-planning-ignore-rules-are-offered-never-installed.md +0 -84
- package/docs/adr/0009-teardown-authority-is-stateless-repository-classification.md +0 -101
- package/docs/adr/0010-model-roster-replaces-the-optimization-dial.md +0 -87
- package/docs/agents/board-sync.md +0 -74
- package/docs/agents/code-review.md +0 -14
- package/docs/agents/workflow-capabilities.json +0 -28
- package/docs/research/agent-task-taxonomy-benchmark-coverage.md +0 -451
- package/docs/research/benchlm-routing-source.md +0 -198
- package/docs/research/consumer-owned-protocol-files.md +0 -238
- package/docs/research/frontend-agent-benchmarks.md +0 -282
- package/docs/research/model-effort-routing-benchmarks.md +0 -261
- package/docs/research/provider-neutral-agent-routing.md +0 -207
- package/docs/research/wave-152-consumer-acceptance.md +0 -98
- package/docs/research/wave-43-script-hook-census.md +0 -128
- package/scripts/build-kit.mjs +0 -117
- package/scripts/build-kit.test.mjs +0 -207
- package/scripts/census/census.test.mjs +0 -252
- package/scripts/census/state.test.mjs +0 -15
- package/scripts/census/transaction.test.mjs +0 -103
- package/scripts/census-contract.test.mjs +0 -227
- package/scripts/check-kit-staleness.mjs +0 -45
- package/scripts/check-kit-staleness.test.mjs +0 -19
- package/scripts/codex-exec-scenarios/fake-codex.mjs +0 -165
- package/scripts/codex-exec.test.mjs +0 -871
- package/scripts/grill-census-wiring-guard.mjs +0 -87
- package/scripts/grill-census-wiring-guard.test.mjs +0 -107
- package/scripts/kit-release.test.mjs +0 -273
- package/scripts/kit-update-pr.test.mjs +0 -151
- package/scripts/lib/audit-refs.mjs +0 -146
- package/scripts/lib/scrub.mjs +0 -113
- package/scripts/lib/scrub.test.mjs +0 -187
- package/scripts/memory-lifecycle/memory-lifecycle.test.mjs +0 -170
- package/scripts/portability_profile_scan.py +0 -53
- package/scripts/release-delta-guard.test.mjs +0 -174
- package/scripts/release-parity.test.mjs +0 -90
- package/scripts/release-state.test.mjs +0 -360
- package/scripts/test_anchor_table.py +0 -69
- package/scripts/test_board_bootstrap.py +0 -348
- package/scripts/test_board_sync.py +0 -208
- package/scripts/test_board_sync_create_idempotency.py +0 -117
- package/scripts/test_board_sync_wave_title.py +0 -120
- package/scripts/test_census_backstop.py +0 -712
- package/scripts/test_census_forward_contract.py +0 -91
- package/scripts/test_census_update_contract.test.mjs +0 -454
- package/scripts/test_codex_adapter_sync_contract.py +0 -169
- package/scripts/test_dist_kit_smoke.py +0 -220
- package/scripts/test_drift_guard_diagnostics.py +0 -295
- package/scripts/test_issue_claim_contract.py +0 -169
- package/scripts/test_kit_docs_language_census.py +0 -178
- package/scripts/test_marker_lib.py +0 -162
- package/scripts/test_orchestrate_wave_contract.py +0 -381
- package/scripts/test_pr_body_check.py +0 -264
- package/scripts/test_profile_globs.py +0 -280
- package/scripts/test_program_planning_contract.py +0 -133
- package/scripts/test_release_authorization_contract.py +0 -101
- package/scripts/test_render_anchor.py +0 -267
- package/scripts/test_retro_wrapup_contract.py +0 -153
- package/scripts/test_skill_code_review_seed.py +0 -89
- package/scripts/test_skill_codex_exec_lifecycle.py +0 -123
- package/scripts/test_skill_frontmatter_lint.py +0 -234
- package/scripts/test_skill_gh_lint.py +0 -124
- package/scripts/test_skill_language_census.py +0 -254
- package/scripts/test_skill_optional_readiness.py +0 -171
- package/scripts/test_skill_portability_lint.py +0 -866
- package/scripts/test_skill_precommit_template.py +0 -30
- package/scripts/test_skill_publish_audit.py +0 -358
- package/scripts/test_skill_readiness_contract.py +0 -264
- package/scripts/test_skill_readiness_preflight.py +0 -180
- package/scripts/test_skill_required_readiness.py +0 -233
- package/scripts/test_skill_selfcontainment_lint.py +0 -255
- package/scripts/test_skill_setup_workflow_seeds.py +0 -691
- package/scripts/test_skill_stale_name_lint.py +0 -126
- package/scripts/test_skill_surface_refs.py +0 -300
- package/scripts/test_skill_trailing_artifact_lint.py +0 -71
- package/scripts/test_tdd_contract.py +0 -78
- package/scripts/test_worktree_ignore_seed.py +0 -320
- package/scripts/test_worktree_setup_base_guard.py +0 -140
- package/scripts/test_worktree_wrapup_contract.py +0 -1737
- package/scripts/test_wrapup_land.py +0 -428
- package/scripts/worktree-lifecycle/session.py +0 -1857
package/scripts/profile_globs.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""One repository-relative glob dialect for every consumer workflow profile.
|
|
3
3
|
|
|
4
|
-
Workflow Advisories
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
Workflow Advisories selects repository-relative paths from consumer-owned
|
|
5
|
+
profile globs, and this is the single matcher it uses, so a pattern can never
|
|
6
|
+
select one set of paths on one advisory surface and a different set on another.
|
|
7
|
+
No Worktree Lifecycle decision reads a glob at all: deletion policy has exactly
|
|
8
|
+
one configuration surface, the ignore mechanism, so no consumer pattern here
|
|
9
|
+
carries deletion authority.
|
|
8
10
|
|
|
9
11
|
Dialect
|
|
10
12
|
-------
|
|
@@ -24,9 +26,9 @@ Run this module to review an installed profile before trusting it:
|
|
|
24
26
|
python3 scripts/profile_globs.py [docs/agents/workflow-capabilities.json]
|
|
25
27
|
|
|
26
28
|
It names every pattern whose match set narrows or widens against the legacy
|
|
27
|
-
matcher
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
matcher and never edits the profile. Exit code 0 means nothing changed meaning,
|
|
30
|
+
1 means at least one pattern needs review, 2 means the profile could not be
|
|
31
|
+
read.
|
|
30
32
|
"""
|
|
31
33
|
|
|
32
34
|
from __future__ import annotations
|
|
@@ -94,7 +96,6 @@ class PatternMigration:
|
|
|
94
96
|
class ProfileGlobFinding:
|
|
95
97
|
location: str
|
|
96
98
|
pattern: str
|
|
97
|
-
deletion_authority: bool
|
|
98
99
|
migration: PatternMigration
|
|
99
100
|
|
|
100
101
|
|
|
@@ -232,52 +233,34 @@ def _surface_globs(advisories: dict, section: str) -> list[tuple[str, str]]:
|
|
|
232
233
|
]
|
|
233
234
|
|
|
234
235
|
|
|
235
|
-
def _profile_globs(document: object) -> list[tuple[str, str
|
|
236
|
+
def _profile_globs(document: object) -> list[tuple[str, str]]:
|
|
236
237
|
"""List every shipped consumer-profile glob call site's configured patterns."""
|
|
237
238
|
if not isinstance(document, dict):
|
|
238
239
|
return []
|
|
239
|
-
globs: list[tuple[str, str, bool]] = []
|
|
240
|
-
for index, pattern in _string_entries(
|
|
241
|
-
document.get("worktreeLifecycle"), "scratchPatterns",
|
|
242
|
-
):
|
|
243
|
-
globs.append((f"worktreeLifecycle.scratchPatterns[{index}]", pattern, True))
|
|
244
|
-
for index, pattern in _string_entries(
|
|
245
|
-
document.get("wrapup"), "landingGeneratedArtifactPatterns",
|
|
246
|
-
):
|
|
247
|
-
globs.append(
|
|
248
|
-
(f"wrapup.landingGeneratedArtifactPatterns[{index}]", pattern, True),
|
|
249
|
-
)
|
|
250
240
|
advisories = document.get("workflowAdvisories")
|
|
251
|
-
if isinstance(advisories, dict):
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
for location, pattern in _surface_globs(advisories, section)
|
|
260
|
-
)
|
|
241
|
+
if not isinstance(advisories, dict):
|
|
242
|
+
return []
|
|
243
|
+
globs: list[tuple[str, str]] = [
|
|
244
|
+
(f"workflowAdvisories.baseline.sourceGlobs[{index}]", pattern)
|
|
245
|
+
for index, pattern in _string_entries(advisories.get("baseline"), "sourceGlobs")
|
|
246
|
+
]
|
|
247
|
+
for section in ("preRefactor", "stopChecks"):
|
|
248
|
+
globs.extend(_surface_globs(advisories, section))
|
|
261
249
|
return globs
|
|
262
250
|
|
|
263
251
|
|
|
264
252
|
def scan_profile(document: object) -> tuple[ProfileGlobFinding, ...]:
|
|
265
253
|
"""Classify every consumer-profile glob against its own legacy matcher.
|
|
266
254
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
255
|
+
Every remaining glob key is a Workflow Advisories key, and those went
|
|
256
|
+
through the case-normalizing `fnmatch`, so all of them can narrow on a
|
|
257
|
+
case-insensitive host.
|
|
270
258
|
"""
|
|
271
259
|
return tuple(
|
|
272
260
|
ProfileGlobFinding(
|
|
273
|
-
location,
|
|
274
|
-
pattern,
|
|
275
|
-
deletion_authority,
|
|
276
|
-
classify_pattern(
|
|
277
|
-
pattern, case_insensitive_legacy=not deletion_authority,
|
|
278
|
-
),
|
|
261
|
+
location, pattern, classify_pattern(pattern, case_insensitive_legacy=True),
|
|
279
262
|
)
|
|
280
|
-
for location, pattern
|
|
263
|
+
for location, pattern in _profile_globs(document)
|
|
281
264
|
)
|
|
282
265
|
|
|
283
266
|
|
|
@@ -290,9 +273,8 @@ def render_report(findings: tuple[ProfileGlobFinding, ...], source: str) -> str:
|
|
|
290
273
|
f"{len(changed)} change meaning.",
|
|
291
274
|
]
|
|
292
275
|
for finding in changed:
|
|
293
|
-
authority = " [deletion authority]" if finding.deletion_authority else ""
|
|
294
276
|
lines.append("")
|
|
295
|
-
lines.append(f"{finding.location}
|
|
277
|
+
lines.append(f"{finding.location}: {finding.pattern}")
|
|
296
278
|
for effect in finding.migration.effects:
|
|
297
279
|
witness = finding.migration.witnesses[effect]
|
|
298
280
|
verb = "now also matches" if effect == WIDENS else "no longer matches"
|
|
@@ -301,8 +283,8 @@ def render_report(findings: tuple[ProfileGlobFinding, ...], source: str) -> str:
|
|
|
301
283
|
lines.append("")
|
|
302
284
|
lines.append(
|
|
303
285
|
"Review each line above and rewrite the pattern yourself. A widened "
|
|
304
|
-
"
|
|
305
|
-
"
|
|
286
|
+
"pattern expands the surface an advisory selects; this check never "
|
|
287
|
+
"edits the profile and never migrates a pattern for you.",
|
|
306
288
|
)
|
|
307
289
|
return "\n".join(lines) + "\n"
|
|
308
290
|
|
|
@@ -331,7 +313,6 @@ def main(argv: list[str]) -> int:
|
|
|
331
313
|
{
|
|
332
314
|
"location": finding.location,
|
|
333
315
|
"pattern": finding.pattern,
|
|
334
|
-
"deletionAuthority": finding.deletion_authority,
|
|
335
316
|
"effects": list(finding.migration.effects),
|
|
336
317
|
"witnesses": finding.migration.witnesses,
|
|
337
318
|
}
|
|
@@ -4,7 +4,7 @@ import { mkdir, mkdtemp, readFile, readdir, rm } from 'node:fs/promises';
|
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { dirname, join } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
|
-
import { STUB_TARGETS } from '../src/lib/bundle.mjs';
|
|
7
|
+
import { STUB_TARGETS, isPublishExcluded } from '../src/lib/bundle.mjs';
|
|
8
8
|
import { sha256 } from '../src/lib/hash.mjs';
|
|
9
9
|
import { buildKit } from './build-kit.mjs';
|
|
10
10
|
|
|
@@ -31,9 +31,20 @@ function mergeDeltas(...deltas) {
|
|
|
31
31
|
]));
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Recommend the smallest bump the delta justifies.
|
|
36
|
+
*
|
|
37
|
+
* `initialDevelopment` is Semver's major-version-zero rule: while the package
|
|
38
|
+
* is still `0.y.z` there is no stable public contract to break, so a removal
|
|
39
|
+
* that would demand a major on a released package demands a minor here. The
|
|
40
|
+
* protection that matters at 0.x survives either way — a removal can never be
|
|
41
|
+
* a patch. Callers pass the BASE version's major, so the recommendation cannot
|
|
42
|
+
* be softened by choosing the target version.
|
|
43
|
+
*/
|
|
44
|
+
export function recommendBump(delta, { minorRemovals = [], initialDevelopment = false } = {}) {
|
|
35
45
|
const minorRemovalSet = new Set(minorRemovals);
|
|
36
|
-
|
|
46
|
+
const breaking = delta.removed.some((path) => !minorRemovalSet.has(path));
|
|
47
|
+
if (breaking) return initialDevelopment ? 'minor' : 'major';
|
|
37
48
|
if (delta.added.length || delta.removed.length) return 'minor';
|
|
38
49
|
return delta.changed.length ? 'patch' : null;
|
|
39
50
|
}
|
|
@@ -81,13 +92,19 @@ export function assessRelease(input) {
|
|
|
81
92
|
+ `tag and publish it before preparing ${input.currentVersion}`,
|
|
82
93
|
);
|
|
83
94
|
}
|
|
84
|
-
// Project-layer stubs are seed inputs, never installed Consumer files
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
95
|
+
// Project-layer stubs are seed inputs, never installed Consumer files, and
|
|
96
|
+
// publish-excluded sources (tests, build tooling, maintainer docs) are never
|
|
97
|
+
// installed either. Removing an accidentally packed copy of either is
|
|
98
|
+
// additive hygiene, unless the same path also disappeared from the actual
|
|
99
|
+
// Consumer bundle.
|
|
100
|
+
const payloadOnlyRemovals = packageDelta.removed.filter(
|
|
101
|
+
(path) => (STUB_TARGETS.includes(path) || isPublishExcluded(path))
|
|
102
|
+
&& !bundleDelta.removed.includes(path),
|
|
89
103
|
);
|
|
90
|
-
const recommendedBump = recommendBump(delta, {
|
|
104
|
+
const recommendedBump = recommendBump(delta, {
|
|
105
|
+
minorRemovals: payloadOnlyRemovals,
|
|
106
|
+
initialDevelopment: Number(input.baseVersion.split('.')[0]) === 0,
|
|
107
|
+
});
|
|
91
108
|
const actual = bumpKind(input.baseVersion, input.currentVersion);
|
|
92
109
|
const rank = { patch: 1, minor: 2, major: 3 };
|
|
93
110
|
if (hasDelta(delta) && input.currentVersion !== input.baseVersion && !actual) {
|
|
@@ -13,35 +13,32 @@ they do not carry a second branch regex, worktree traversal, or failure policy.
|
|
|
13
13
|
- `enabled`: explicit activation gate.
|
|
14
14
|
- `worktreeRoot`, `branchTemplate`, `pathTemplate`, and `branchRegex`: consumer
|
|
15
15
|
naming and location policy.
|
|
16
|
+
- `contentBranchTemplate` (default `{type}/{slug}`): the issue-less branch a
|
|
17
|
+
session cuts when its output is durable content. It renders `{type}` and
|
|
18
|
+
`{slug}` only — a durable-content session has no issue number — and refuses
|
|
19
|
+
an `{issue}` placeholder instead of inventing one.
|
|
16
20
|
- `mainBranches` and `protectedBranches`: branches guarded in the main checkout.
|
|
17
21
|
- `setupEntry` and ordered `setupSteps`: the portable setup command and project
|
|
18
22
|
setup sequence.
|
|
19
23
|
- `riskyCommandPatterns`: commands that must target the active linked worktree.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
Profile globs
|
|
28
|
-
|
|
29
|
-
`scripts/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
path. For example, `**/__pycache__/**` matches both root and nested caches,
|
|
35
|
-
while `dist-kit/*` does not match `dist-kit/a/b`.
|
|
36
|
-
|
|
37
|
-
Because both capabilities load the same matcher, a pattern can never select one
|
|
38
|
-
set of paths for an advisory and a different set for a deletion decision. Run
|
|
24
|
+
|
|
25
|
+
The profile carries **structural facts only**. It declares no pattern list,
|
|
26
|
+
because deletion policy has exactly one configuration surface: the ignore
|
|
27
|
+
mechanism. Making a file deletable at teardown means ignoring it.
|
|
28
|
+
Keys this loader does not know are ignored in silence, so a profile written for
|
|
29
|
+
an older kit keeps working without warning noise.
|
|
30
|
+
|
|
31
|
+
Profile globs elsewhere in the kit (Workflow Advisories) use one
|
|
32
|
+
repository-relative POSIX dialect, implemented once in
|
|
33
|
+
`scripts/profile_globs.py`. `*` and `?` stay inside one path segment;
|
|
34
|
+
`[seq]`/`[!seq]` are per-segment character classes; `**` as a whole segment
|
|
35
|
+
matches zero or more segments, so a leading `**/` also matches the repository
|
|
36
|
+
root and `dir/**` also matches `dir` itself; matching is always case-sensitive
|
|
37
|
+
on every host filesystem; and a pattern must match the whole path. Run
|
|
39
38
|
`python3 scripts/profile_globs.py <profile.json>` to review an installed
|
|
40
|
-
profile
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
profile and never rewrites a pattern, so a migration cannot silently expand
|
|
44
|
-
cleanup authority.
|
|
39
|
+
profile. No Worktree Lifecycle decision reads a glob: the ignore mechanism is
|
|
40
|
+
the single deletion-policy surface, so a pattern can no longer widen cleanup
|
|
41
|
+
authority at all.
|
|
45
42
|
|
|
46
43
|
Unknown or malformed events fail open without changing repository state.
|
|
47
44
|
Security-sensitive, profile-matched edits and commands fail closed only when
|
|
@@ -66,157 +63,57 @@ shipped skills write (`PLAN.md`, `PLAN-REVIEW-LOG.md`, `ANNAHMEN.md`).
|
|
|
66
63
|
`preview` reports what an approval would append and writes nothing, and `apply`
|
|
67
64
|
appends exactly one marker block. `.gitignore` is a consumer file the kit does
|
|
68
65
|
not own, so only an explicit approved `/setup-workflow` step may run `apply`;
|
|
69
|
-
`init` and `update` reconciliation never reach it
|
|
66
|
+
`init` and `update` reconciliation never reach it.
|
|
70
67
|
|
|
71
68
|
The append never rewrites, reorders, or removes an existing line. A repository
|
|
72
69
|
that already ignores every artefact reports `nothing-to-do`, a re-run after an
|
|
73
70
|
approval is a byte-identical no-op, and a marker block the consumer has since
|
|
74
71
|
edited reports `blocked` rather than being repaired. An artefact already tracked
|
|
75
72
|
in git is named separately — an ignore rule cannot untrack it, and the helper
|
|
76
|
-
never runs `git rm`.
|
|
77
|
-
|
|
73
|
+
never runs `git rm`. Approving that offer is also what makes the artefacts
|
|
74
|
+
deletable at teardown: `.gitignore` is the one deletion-policy surface.
|
|
78
75
|
|
|
79
76
|
## Cleanup
|
|
80
77
|
|
|
81
|
-
`
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
identity-bound landing candidates; it authorizes no deletion. After merge and
|
|
115
|
-
`fetch origin/main`, cleanup reloads the profile directly from canonical
|
|
116
|
-
`origin/main`, requires its scratch and generator policies to equal the
|
|
117
|
-
worktree candidate and the policy digest frozen at attempt start, and only then
|
|
118
|
-
authorizes mutation. Every supplied generator-evidence path is independently
|
|
119
|
-
checked against that canonical generator policy. A missing policy is distinct
|
|
120
|
-
from an explicit empty policy. An unmerged or transient branch policy therefore
|
|
121
|
-
cannot grant itself broader cleanup authority.
|
|
122
|
-
|
|
123
|
-
Canonical policy that drifts between attempt start and post-merge cleanup keeps
|
|
124
|
-
that refusal, and the refusal names its supported recovery. Recovery re-derives
|
|
125
|
-
authority from the merged canonical policy alone: it never consults the stale
|
|
126
|
-
worktree candidate, never re-scans for new candidates, requires the branch to
|
|
127
|
-
already be an ancestor of canonical main, and requires each frozen identity to
|
|
128
|
-
be both named by canonical policy and unchanged on disk. Evidence outside
|
|
129
|
-
canonical policy, a changed identity, and pre-existing or foreign state stop the
|
|
130
|
-
recovery instead of being deleted, and a repeated run after a completed teardown
|
|
131
|
-
is a no-op.
|
|
132
|
-
|
|
133
|
-
The landing adapter may carry exact scratch evidence only for current ignored
|
|
134
|
-
files that match the consumer-owned
|
|
135
|
-
`wrapup.landingGeneratedArtifactPatterns` profile and were absent from that
|
|
136
|
-
creation baseline. Missing, changed, or incoherent provenance stops landing
|
|
137
|
-
cleanup. Initial/profile-matched files, unmatched files, landing-generated
|
|
138
|
-
symlinks, and writes after the landing evidence snapshot remain cleanup stops;
|
|
139
|
-
generator evidence and regular-file removal stay descriptor-bound and require
|
|
140
|
-
a second inventory check.
|
|
141
|
-
Mutable session logs belong in explicit `scratchPatterns`, not in the landing
|
|
142
|
-
generator allowlist: their identity is frozen at final cleanup assessment, so
|
|
143
|
-
normal logging before teardown remains live while a later append or replacement
|
|
144
|
-
still stops deletion.
|
|
145
|
-
Landing-start blockers are classified before a journal is written, so moving a
|
|
146
|
-
protected blocker permits a clean next attempt. An explicit relinquish archives
|
|
147
|
-
either a started or frozen attempt, including drifted evidence, without
|
|
148
|
-
deleting or claiming any file; the next preflight treats every current file as
|
|
149
|
-
pre-existing. Exact unchanged frozen evidence remains directly resumable. The
|
|
150
|
-
attempt journal name is classified without following a symlink, so a symlinked
|
|
151
|
-
or dangling journal entry stops instead of being read or replaced, and each
|
|
152
|
-
archived receipt is filed under a contract-version-neutral stem plus its own
|
|
153
|
-
recorded contract version. A journal written under a superseded contract is
|
|
154
|
-
classified as legacy rather than corrupt when it still satisfies its own
|
|
155
|
-
recorded contract; that refusal names the archive route explicitly, while a
|
|
156
|
-
journal that fails its own contract is still reported as incoherent evidence.
|
|
78
|
+
`classify.py` is the teardown authority. It reads the worktree's current state
|
|
79
|
+
at the moment of action and nothing else: a tracked change or an unmerged path
|
|
80
|
+
blocks, an untracked non-ignored file blocks with a bounded report (count plus
|
|
81
|
+
top directories, never a path dump), and an ignored entry is Scratch and
|
|
82
|
+
deletable. The single hardcoded exception is `.env*` by basename glob, which is
|
|
83
|
+
deletable only when it is byte-identical to its counterpart at the same
|
|
84
|
+
relative path in the main checkout, both opened no-follow. An ignored symlink
|
|
85
|
+
is deletable only when its target resolves inside the assessed worktree; the
|
|
86
|
+
link itself is unlinked and never followed.
|
|
87
|
+
|
|
88
|
+
`cleanup.py` previews by default. Removal additionally refuses an unregistered
|
|
89
|
+
path, a detached branch, a protected branch or the main checkout, an open PR,
|
|
90
|
+
and an unmerged branch. The assessment reads `ANNAHMEN.md` before removal and
|
|
91
|
+
returns its contents for propagation.
|
|
92
|
+
|
|
93
|
+
Explicit removal re-collects facts immediately before mutation and requires the
|
|
94
|
+
same removable inventory. The deletion walk re-opens the assessed root
|
|
95
|
+
no-follow, re-checks its device/inode, and re-checks every entry's kind — and a
|
|
96
|
+
symlink's target — immediately before unlinking it. It uses ordinary `git
|
|
97
|
+
worktree remove` and never bypasses Git's final concurrent-change check with
|
|
98
|
+
force removal.
|
|
99
|
+
|
|
100
|
+
`wrapup-land.py` runs the same assessment after a merge: quiesce the worktree's
|
|
101
|
+
own declared `.dev-ports` listeners, classify, delete the Scratch, remove the
|
|
102
|
+
worktree. Teardown always runs — a direct `/wrapup` invocation is its
|
|
103
|
+
authorization, including for a worktree an external tool created under a
|
|
104
|
+
foreign name and path. There is no persisted attempt state and no recovery
|
|
105
|
+
flag: an interrupted landing is resumed by re-running it, because every step
|
|
106
|
+
verifies present state and skips what is already done.
|
|
107
|
+
|
|
108
|
+
Two residual risks are accepted deliberately — between assessment and deletion
|
|
109
|
+
a file could in principle be replaced, and a valuable file a consumer keeps
|
|
110
|
+
gitignored outside `.env*` is deletable at teardown.
|
|
157
111
|
|
|
158
112
|
`cleanup.py sweep` is the read-only inventory entrypoint. It accounts once for
|
|
159
113
|
every linked worktree and local branch, reports issue/PR/merge/age/removal
|
|
160
114
|
facts, and counts merged remote branches separately. It never removes a
|
|
161
115
|
worktree or branch.
|
|
162
116
|
|
|
163
|
-
## Session-owned teardown
|
|
164
|
-
|
|
165
|
-
`session.py` is the narrower orchestration route. It binds one receipt in the
|
|
166
|
-
Git common directory to the active `wave-active/<anchor>` annotated claim:
|
|
167
|
-
|
|
168
|
-
```sh
|
|
169
|
-
python3 scripts/worktree-lifecycle/session.py begin --anchor <n> --owner <run> --base <wave-head>
|
|
170
|
-
python3 scripts/worktree-lifecycle/session.py create --anchor <n> --owner <run> --base <current-wave-head> <issue> <slug> <type>
|
|
171
|
-
python3 scripts/worktree-lifecycle/session.py recover --anchor <n> --owner <run> --branch <exact-branch>
|
|
172
|
-
python3 scripts/worktree-lifecycle/session.py seal --anchor <n> --owner <run>
|
|
173
|
-
python3 scripts/worktree-lifecycle/session.py inspect --anchor <n> --owner <run> --main origin/main
|
|
174
|
-
python3 scripts/worktree-lifecycle/session.py teardown --anchor <n> --owner <run> --main origin/main
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
Only `create` can add an inventory row, and it refuses any branch or path that
|
|
178
|
-
already existed. It journals a provisional exact row before `git worktree add`,
|
|
179
|
-
then captures the shared artifact baseline before project setup and promotes
|
|
180
|
-
the row only after setup succeeds. Session inspection
|
|
181
|
-
therefore accepts generated scratch only when the exact path matches the
|
|
182
|
-
profile and is absent from the creation baseline; missing, changed, or
|
|
183
|
-
incoherent provenance stops. `seal` records the final exact branch OIDs.
|
|
184
|
-
Inspection reports ancestry, one-to-one stable patch equivalence, unique
|
|
185
|
-
content, and ambiguity separately. Empty commits, non-ancestry merge commits,
|
|
186
|
-
duplicate patch IDs, open or unreadable PR evidence, dirt, protected names,
|
|
187
|
-
stale registrations, recreated removed targets, and identity drift stop the
|
|
188
|
-
whole teardown before mutation.
|
|
189
|
-
|
|
190
|
-
If setup fails, the provisional row becomes `recovery-pending`. Automatic and
|
|
191
|
-
explicit `recover` use the same bounded route: active claim and receipt,
|
|
192
|
-
protected name, PR state, exact ref OID, registration/root identity, and the
|
|
193
|
-
creation baseline are revalidated. A branch-only remainder is compare-deleted
|
|
194
|
-
only at its provisional OID. A registered worktree whose add succeeded before
|
|
195
|
-
root journaling is adopted only when its Git registration/backlink, branch,
|
|
196
|
-
OID, and exact-clean inventory all match.
|
|
197
|
-
|
|
198
|
-
For failed project setup, exact index/worktree diff hashes and paths are frozen
|
|
199
|
-
without storing diff content. Exact regular-file or no-follow symlink identity
|
|
200
|
-
is frozen for every setup-created untracked path. Recovery revalidates that
|
|
201
|
-
evidence, restores only the frozen tracked paths to the creation OID, and
|
|
202
|
-
unlinks only matching untracked identities; later foreign files,
|
|
203
|
-
modifications, or replacements stop without losing receipt ownership. If
|
|
204
|
-
identity capture cannot finish, the receipt retains only a bounded failure
|
|
205
|
-
class and can retry from the unchanged inventory or an exact-clean worktree.
|
|
206
|
-
Raw command, exception, stdout, and stderr text is never retained or archived.
|
|
207
|
-
Recovery uses ordinary worktree removal and compare-deletes the exact ref.
|
|
208
|
-
|
|
209
|
-
Teardown re-runs that complete assessment, archives every recovery OID in the
|
|
210
|
-
receipt before its first mutation, and revalidates canonical main, the active
|
|
211
|
-
claim and receipt, profile protection, PR state, branch OID, worktree
|
|
212
|
-
registration/root identity, dirt, and the exact scratch inventory immediately
|
|
213
|
-
before each target mutation. After ordinary worktree removal it rechecks the
|
|
214
|
-
ref-side gates, then compare-deletes only the recorded local refs with
|
|
215
|
-
`git update-ref -d <ref> <expected-oid>`. A concurrent ref move therefore
|
|
216
|
-
survives, while a partial run keeps its recovery OIDs and can resume. Generic
|
|
217
|
-
cleanup and sweep never consume this ownership route and never infer authority
|
|
218
|
-
over a foreign branch.
|
|
219
|
-
|
|
220
117
|
Claude hook wiring and any Codex adaptation consume this same profile and core.
|
|
221
118
|
An adapter may change only the surface event envelope; it must preserve the
|
|
222
119
|
core verdict and message.
|
|
@@ -39,13 +39,12 @@
|
|
|
39
39
|
{
|
|
40
40
|
"historicalPath": "scripts/cleanup-worktrees.sh",
|
|
41
41
|
"artifact": "scripts/worktree-lifecycle/cleanup.py",
|
|
42
|
-
"supportingArtifacts": ["scripts/worktree-lifecycle/
|
|
42
|
+
"supportingArtifacts": ["scripts/worktree-lifecycle/classify.py"],
|
|
43
43
|
"primitives": [
|
|
44
44
|
"profile",
|
|
45
45
|
"git-facts",
|
|
46
46
|
"cleanup-safety",
|
|
47
|
-
"
|
|
48
|
-
"patch-equivalence",
|
|
47
|
+
"stateless-classification",
|
|
49
48
|
"compare-and-delete"
|
|
50
49
|
]
|
|
51
50
|
}
|