@aperant/framework 0.7.4 → 0.8.1

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.
Files changed (119) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +12 -0
  3. package/agents/apt-executor.md +12 -0
  4. package/agents/apt-team-docs-narrator.md +11 -0
  5. package/dist/cli/ci-watch/stop-matrix.d.mts +17 -4
  6. package/dist/cli/ci-watch/stop-matrix.d.mts.map +1 -1
  7. package/dist/cli/ci-watch/stop-matrix.mjs +41 -4
  8. package/dist/cli/ci-watch/stop-matrix.mjs.map +1 -1
  9. package/dist/cli/commands/fan-out.d.mts +2 -0
  10. package/dist/cli/commands/fan-out.d.mts.map +1 -0
  11. package/dist/cli/commands/fan-out.mjs +123 -0
  12. package/dist/cli/commands/fan-out.mjs.map +1 -0
  13. package/dist/cli/commands/init.d.mts.map +1 -1
  14. package/dist/cli/commands/init.mjs +67 -27
  15. package/dist/cli/commands/init.mjs.map +1 -1
  16. package/dist/cli/commands/modes.d.mts +22 -0
  17. package/dist/cli/commands/modes.d.mts.map +1 -1
  18. package/dist/cli/commands/modes.mjs +4 -1
  19. package/dist/cli/commands/modes.mjs.map +1 -1
  20. package/dist/cli/commands/pr-review-audit-fixer.d.mts.map +1 -1
  21. package/dist/cli/commands/pr-review-audit-fixer.mjs +86 -6
  22. package/dist/cli/commands/pr-review-audit-fixer.mjs.map +1 -1
  23. package/dist/cli/commands/roadmap.d.mts +1 -1
  24. package/dist/cli/commands/roadmap.d.mts.map +1 -1
  25. package/dist/cli/commands/roadmap.mjs +47 -4
  26. package/dist/cli/commands/roadmap.mjs.map +1 -1
  27. package/dist/cli/commands/task.d.mts.map +1 -1
  28. package/dist/cli/commands/task.mjs +15 -0
  29. package/dist/cli/commands/task.mjs.map +1 -1
  30. package/dist/cli/commands/validate-evidence.d.mts +1 -1
  31. package/dist/cli/commands/validate-evidence.d.mts.map +1 -1
  32. package/dist/cli/commands/validate-evidence.mjs +72 -47
  33. package/dist/cli/commands/validate-evidence.mjs.map +1 -1
  34. package/dist/cli/commands/wave.d.mts.map +1 -1
  35. package/dist/cli/commands/wave.mjs +91 -61
  36. package/dist/cli/commands/wave.mjs.map +1 -1
  37. package/dist/cli/commands/worktree.mjs +62 -3
  38. package/dist/cli/commands/worktree.mjs.map +1 -1
  39. package/dist/cli/config/load.mjs +1 -1
  40. package/dist/cli/config/load.mjs.map +1 -1
  41. package/dist/cli/config/parallelization.d.mts +14 -0
  42. package/dist/cli/config/parallelization.d.mts.map +1 -0
  43. package/dist/cli/config/parallelization.mjs +19 -0
  44. package/dist/cli/config/parallelization.mjs.map +1 -0
  45. package/dist/cli/coordination/agent-identity.d.mts.map +1 -1
  46. package/dist/cli/coordination/agent-identity.mjs +7 -1
  47. package/dist/cli/coordination/agent-identity.mjs.map +1 -1
  48. package/dist/cli/design/frontmatter-schema.d.mts +4 -4
  49. package/dist/cli/dispatch.d.mts.map +1 -1
  50. package/dist/cli/dispatch.mjs +2 -0
  51. package/dist/cli/dispatch.mjs.map +1 -1
  52. package/dist/cli/install/cli-to-install-root.d.mts.map +1 -1
  53. package/dist/cli/install/cli-to-install-root.mjs +0 -2
  54. package/dist/cli/install/cli-to-install-root.mjs.map +1 -1
  55. package/dist/cli/install/runtime-detect.d.mts +2 -6
  56. package/dist/cli/install/runtime-detect.d.mts.map +1 -1
  57. package/dist/cli/install/runtime-detect.mjs +8 -3
  58. package/dist/cli/install/runtime-detect.mjs.map +1 -1
  59. package/dist/cli/install/transforms/pi.d.mts +6 -0
  60. package/dist/cli/install/transforms/pi.d.mts.map +1 -0
  61. package/dist/cli/install/transforms/pi.mjs +37 -0
  62. package/dist/cli/install/transforms/pi.mjs.map +1 -0
  63. package/dist/cli/personas/sidecar.d.mts +1 -1
  64. package/dist/cli/roadmap/convex-mirror.d.mts +21 -0
  65. package/dist/cli/roadmap/convex-mirror.d.mts.map +1 -0
  66. package/dist/cli/roadmap/convex-mirror.mjs +133 -0
  67. package/dist/cli/roadmap/convex-mirror.mjs.map +1 -0
  68. package/dist/cli/roadmap/lifecycle.d.mts +1 -1
  69. package/dist/cli/roadmap/lifecycle.d.mts.map +1 -1
  70. package/dist/cli/roadmap/lifecycle.mjs +54 -0
  71. package/dist/cli/roadmap/lifecycle.mjs.map +1 -1
  72. package/dist/cli/roadmap/rollup.d.mts +2 -2
  73. package/dist/cli/wfrun/unit-shape.d.mts +27 -0
  74. package/dist/cli/wfrun/unit-shape.d.mts.map +1 -0
  75. package/dist/cli/wfrun/unit-shape.mjs +38 -0
  76. package/dist/cli/wfrun/unit-shape.mjs.map +1 -0
  77. package/dist/plugin/.claude-plugin/plugin.json +2 -1
  78. package/dist/plugin/agents/apt-executor.md +12 -0
  79. package/dist/plugin/agents/apt-team-docs-narrator.md +11 -0
  80. package/dist/plugin/skills/apt/SKILL.md +3 -2
  81. package/dist/plugin/skills/apt-execute/SKILL.md +13 -0
  82. package/dist/plugin/skills/apt-fan-out/SKILL.md +124 -0
  83. package/dist/plugin/skills/apt-pr-review/SKILL.md +8 -1
  84. package/dist/plugin/skills/apt-review/SKILL.md +4 -0
  85. package/dist/plugin/skills/apt-run/SKILL.md +20 -5
  86. package/dist/plugin/skills/apt-spar/SKILL.md +12 -9
  87. package/dist/plugin/skills/apt-verify/SKILL.md +11 -0
  88. package/dist/plugin/skills/apt-watch-ci/SKILL.md +2 -2
  89. package/dist/schemas/quick-task.d.ts +17 -17
  90. package/package.json +133 -133
  91. package/skills/apt/SKILL.md +3 -2
  92. package/skills/apt-execute/SKILL.md +13 -0
  93. package/skills/apt-fan-out/SKILL.md +124 -0
  94. package/skills/apt-review/SKILL.md +4 -0
  95. package/skills/apt-run/SKILL.md +20 -5
  96. package/skills/apt-spar/SKILL.md +12 -9
  97. package/skills/apt-verify/SKILL.md +11 -0
  98. package/skills/apt-watch-ci/SKILL.md +2 -2
  99. package/src/cli/ci-watch/stop-matrix.mjs +44 -4
  100. package/src/cli/commands/fan-out.mjs +129 -0
  101. package/src/cli/commands/init.mjs +66 -27
  102. package/src/cli/commands/modes.mjs +4 -1
  103. package/src/cli/commands/pr-review-audit-fixer.mjs +82 -6
  104. package/src/cli/commands/roadmap.mjs +48 -4
  105. package/src/cli/commands/task.mjs +15 -0
  106. package/src/cli/commands/validate-evidence.mjs +75 -50
  107. package/src/cli/commands/wave.mjs +91 -58
  108. package/src/cli/commands/worktree.mjs +56 -3
  109. package/src/cli/config/load.mjs +1 -1
  110. package/src/cli/config/parallelization.mjs +18 -0
  111. package/src/cli/coordination/agent-identity.mjs +9 -1
  112. package/src/cli/dispatch.mjs +2 -0
  113. package/src/cli/install/cli-to-install-root.mjs +0 -2
  114. package/src/cli/install/runtime-detect.mjs +8 -3
  115. package/src/cli/install/transforms/pi.mjs +37 -0
  116. package/src/cli/roadmap/convex-mirror.mjs +131 -0
  117. package/src/cli/roadmap/lifecycle.mjs +48 -0
  118. package/src/cli/wfrun/unit-shape.mjs +39 -0
  119. package/templates/config.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,71 @@ All notable changes to `@aperant/framework` are documented here. See
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## 0.8.1 — 2026-05-20 — Pi runtime + idempotent re-init repair
9
+
10
+ Patch release making `apt-tools init` safe to rerun on another machine after
11
+ pulling a current `.aperant/config.json`.
12
+
13
+ ### Fixed
14
+
15
+ - Current-schema `init` no longer returns `up_to_date` when explicit runtime
16
+ flags were passed or when suggested runtime roots are missing
17
+ `apt-file-manifest.json`; it reruns the runtime installer for those roots.
18
+ - True no-op behavior is preserved when no runtime roots are suggested and no
19
+ explicit runtime flags are passed.
20
+
21
+ ### Added
22
+
23
+ - Pi is now an installable runtime via `--pi`, with files written under
24
+ `.pi/skills/` and `.pi/prompts/`.
25
+ - `cliToInstallRoot('pi')` now resolves to `.pi`.
26
+
27
+ ### Version
28
+
29
+ - `packages/framework/package.json`: 0.8.0 → 0.8.1
30
+ - CLI golden fixtures refreshed
31
+
32
+ ## 0.8.0 — 2026-05-20 — cross-task fan-out + 5 framework defects (BUG-033..038)
33
+
34
+ Minor release. Ships the new `/apt:fan-out` skill (cross-task parallelization from a single conductor terminal) and closes five framework defects surfaced during real `/apt:run` pipelines. Includes one behavioral default change (`task_isolation.from_branch` now defaults to `main`, not `current`).
35
+
36
+ ### New capability: `/apt:fan-out`
37
+
38
+ Cross-task parallelization from one conductor terminal. The host CLI's main thread spawns N `apt-executor` sub-agents via Task tool `run_in_background: true`, each pinned to its own task-level worktree. Accepts `--tasks <id-list>` (canonical), `--phases <m/p>` (policy front), `--unblocked` (auto-pick from roadmap ready-state). Default concurrency cap `parallelization.max_tasks: 4`.
39
+
40
+ Built on the **generalized wave engine**: `wave.mjs` + `wfrun/dag.mjs` now accept `unit_kind: "subtask" | "task"` so task-level fan-out reuses the same primitives (`apt-tools lock`, JSONL event log, `max_agents` quota) as the existing subtask wave-parallel mode. Subtask path is bit-equivalent (locked by snapshot test).
41
+
42
+ ADRs documenting the locked decisions:
43
+ - ADR-0001 worker subagent topology (host CLI Task tool background spawn)
44
+ - ADR-0002 paired skill surface (mechanism + policy split)
45
+ - ADR-0003 conductor identity (host CLI main thread; never daemon)
46
+
47
+ ### Defects fixed
48
+
49
+ - **FRAMEWORK-BUG-033** — cross-task fan-out gap (the umbrella above) bundled the four lifecycle/identity sub-defects:
50
+ - Phase auto-completion sibling-guard bug in `lifecycle.mjs` (16-task phase flipping to `completed` on first close)
51
+ - Durable `assigned_to` field on task records
52
+ - `APT_AGENT_ID=fanout-${batch_id}-${task_id}` export from `apt-executor` for stable per-worker identity
53
+ - Wave engine generalization to accept either subtask or task as `unit_id`
54
+ - **FRAMEWORK-BUG-034** — `validate-evidence` parser self-reference. Stamper inserted `- **Validation Status**: ...` using the same `- **...**:` field-form shape its own regex matched; on subsequent passes the parser dismissed 88% of LLM-validated findings as `evidence_not_literal` by reading its own stamped text as the cited evidence. Fix: writer emits trailing `### Validation Metadata` section; reader strips legacy field-form Status/Note before evidence extraction. Dual-shape reader for back-compat.
55
+ - **FRAMEWORK-BUG-035** — `pr-review-audit-fixer` parser only matched `### {ID}` finding headers, not the canonical `## Fix N` shape per FRAMEWORK-BUG-030. Returned `<none parsed>` + false-positive `hallucinated` verdict despite real commits. Fix: new `parseFixNEntries` with finding-ID extraction via priority chain `**Finding ID**:` → `**Fix**:` → `**ID**:` → synthetic.
56
+ - **FRAMEWORK-BUG-036** — lifecycle phase didn't auto-advance through `apt:execute` / `apt:verify` / `apt:review` SKILL.md success-paths. Only `/apt:ship` flipped the phase (and only from `reviewing`, which never happened). Fix: each skill adds ONE `task update --lifecycle-phase` call at success-end, advancing `executing` → `verifying` → `reviewing`. `/apt:ship` atomic `reviewing → shipped-pending-merge` flip now fires reliably.
57
+ - **FRAMEWORK-BUG-037** — `task_isolation.from_branch: "current"` default silently chained WIP commits into new task PRs. **Behavioral default change**: now `"main"` with main-detection chain (`origin/HEAD` → `origin/main` → `origin/master` → local `main` / `master`). Explicit `--from-branch current` / `--from-branch HEAD` preserved for power users who want chaining. Projects with explicit `from_branch` in their own `.aperant/config.json` are unaffected.
58
+ - **FRAMEWORK-BUG-038** — `/apt:spar` Round-1 `verdict=cede` terminated unilaterally, collapsing every multi-round session to one round. Two SKILL.md edits: partner prompt template now requires `NEXT_WEAKEST_POINT`; Step 8 splits cede semantics — `--rounds 1` explicit terminates, `--rounds >= 2` (default) emits `next-round` regardless and requires two consecutive same-disposition rounds for convergence. No new flags, no new skills.
59
+
60
+ ### Version
61
+
62
+ - `packages/framework/package.json`: 0.7.4 → 0.8.0
63
+ - `cli-baseline-version.txt` + `cli-baseline-help.txt` fixtures bumped
64
+
65
+ ### Commits
66
+
67
+ - feat(framework): cross-task fan-out + 4 lifecycle/identity fixes (#208) — bd099c8f
68
+ - fix(framework): apt:spar Round-1 collapse (#210) — 567a63d0
69
+ - fix(framework): validate-evidence + audit-fixer parsers (#211) — 0f28a1af
70
+ - fix(framework): lifecycle auto-advance + from-branch default (#214) — b1abcb65
71
+ - (this one) — chore(framework): 0.8.0 — cross-task fan-out + 5 framework defects (BUG-033..038)
72
+
8
73
  ## 0.7.4 — 2026-05-18 — apt-pr-review context-budget skip prohibition
9
74
 
10
75
  Docs-only patch release hardening the `apt-pr-review` "Fix Everything" contract against a live failure pattern where an orchestrator dropped confirmed minor findings mid-Phase-6 because its own context window felt heavy ("my orchestrator window was getting heavy after 6 specialist rounds + Round 1 work"). Pre-existing rule 5 in §Context Budget Rules already forbade skipping *phases*; this release closes the per-finding gap inside Phase 6.
package/README.md CHANGED
@@ -11,6 +11,12 @@
11
11
  npx @aperant/framework init .
12
12
  ```
13
13
 
14
+ For non-interactive install or re-init across common runtimes:
15
+
16
+ ```bash
17
+ npx @aperant/framework init . --claude --codex --pi --yes
18
+ ```
19
+
14
20
  ## Usage
15
21
 
16
22
  Just type `/apt` and describe what you want in natural language. The router classifies your input and dispatches to the right skill.
@@ -25,6 +31,12 @@ Just type `/apt` and describe what you want in natural language. The router clas
25
31
 
26
32
  You can also call any skill directly (e.g. `/apt:plan ...`, `/apt:debug ...`).
27
33
 
34
+ Runtime command surfaces differ slightly:
35
+
36
+ - Claude: `/apt:plan`
37
+ - Codex: `/apt-plan`
38
+ - Pi: `/apt`, `/apt:plan` via `.pi/prompts/`
39
+
28
40
  ## Available commands
29
41
 
30
42
  | Stage | Command | Purpose |
@@ -46,6 +46,18 @@ node packages/framework/bin/apt-tools.mjs gate check . --task-id {task-id} --ski
46
46
 
47
47
  If `status == "fail"`, STOP. Report the failed gates verbatim and exit — do NOT auto-apply `--force`. Forcing is a human decision; the user can re-invoke with `--force --reason "<explanation>"` to bypass (the override is logged to `.aperant/events/{today}.jsonl`).
48
48
 
49
+ ## 0a. Fan-out worker identity (FRAMEWORK-BUG-033 defect 3)
50
+
51
+ If your spawn prompt contains both `batch_id` and `task_id` (i.e. you are a fan-out worker subagent dispatched by `/apt:fan-out`), export `APT_AGENT_ID` in EVERY `Bash` invocation that runs `apt-tools ...`:
52
+
53
+ ```bash
54
+ APT_AGENT_ID=fanout-${batch_id}-${task_id} node packages/framework/bin/apt-tools.mjs <command> ...
55
+ ```
56
+
57
+ This pins your `team-status/{agentId}.json` file across short-lived CLI invocations. Without it, each `apt-tools` call gets a fresh `hostname-pid` agentId (per `agent-identity.mjs:86`), the previous pid dies, and `listTeamStatus({ includeStale: false })` filters healthy workers out between invocations — the team-status dashboard flickers into emptiness instead of showing 4 stable fan-out workers.
58
+
59
+ Non-fan-out invocations of this agent receive no `batch_id` and skip this export — the legacy `hostname-pid` path remains correct for single-task work.
60
+
49
61
  ## 1. Load Context
50
62
 
51
63
  1. Read `CLAUDE.md` and `AGENTS.md` if they exist
@@ -72,6 +72,17 @@ If the parent spawned you without a mode you recognize, write an error line to `
72
72
  rendered table. If present, REPLACE the content between anchors (never
73
73
  append — this is how re-runs stay idempotent).
74
74
  7. Call `apt-tools roadmap sync . --reason 'narrator:phase-ship:<phase_id>'`.
75
+ 8. **Slice 5 / AC14 / ADR-0006 dual-write.** Write the rendered prose
76
+ block to a temp file (e.g. `mktemp -t narrator-prose-XXXXXX.md`), then
77
+ run
78
+ `apt-tools roadmap convex-mirror . --scope <scope> --phase <phase_id> --anchor-id phase-ship:<phase_id> --prose-path <tempfile>`
79
+ (add `--merged-pr <pr-number>` when known). The markdown write from
80
+ step 7 is canonical (ADR-0006); this step is the Convex mirror.
81
+ Fail-soft contract:
82
+ - exit 0 with `status:ok` or `status:queued` → continue normally
83
+ - exit non-zero with `status:error` → append the error message to
84
+ `.aperant/digests/errors.log` and continue (do NOT block on Convex
85
+ failures; the in-process cron drainer reconciles within 5 min)
75
86
 
76
87
  ## Mode 2: `decision-merge` — merge a recorded decision into the roadmap
77
88
 
@@ -1,8 +1,13 @@
1
1
  /**
2
2
  * @typedef {Object} CheckRow
3
3
  * @property {string} [name]
4
- * @property {string} [conclusion] e.g. 'success' | 'failure' | 'cancelled' | 'timed_out' | 'neutral'
5
- * @property {string} [state]
4
+ * @property {string} [state] Primary per-check status (gh-CLI uppercase enum):
5
+ * 'SUCCESS' | 'FAILURE' | 'CANCELLED' | 'TIMED_OUT' | 'SKIPPED' |
6
+ * 'IN_PROGRESS' | 'QUEUED' | 'PENDING' | 'NEUTRAL'.
7
+ * @property {string} [conclusion] Legacy lowercase per-check field
8
+ * (deprecated — gh dropped it from `pr checks --json` schema circa 2.65).
9
+ * Retained only as a soft fallback for in-memory test fixtures
10
+ * (FRAMEWORK-BUG-016).
6
11
  * @property {string} [bucket]
7
12
  * @property {string} [failure_class] — optional pre-classified hint (test-only seam)
8
13
  * @property {string} [log_fingerprint] — optional digest for identical-log detection
@@ -25,10 +30,18 @@ export function isFailing(check: any): boolean;
25
30
  export type CheckRow = {
26
31
  name?: string | undefined;
27
32
  /**
28
- * e.g. 'success' | 'failure' | 'cancelled' | 'timed_out' | 'neutral'
33
+ * Primary per-check status (gh-CLI uppercase enum):
34
+ * 'SUCCESS' | 'FAILURE' | 'CANCELLED' | 'TIMED_OUT' | 'SKIPPED' |
35
+ * 'IN_PROGRESS' | 'QUEUED' | 'PENDING' | 'NEUTRAL'.
29
36
  */
30
- conclusion?: string | undefined;
31
37
  state?: string | undefined;
38
+ /**
39
+ * Legacy lowercase per-check field
40
+ * (deprecated — gh dropped it from `pr checks --json` schema circa 2.65).
41
+ * Retained only as a soft fallback for in-memory test fixtures
42
+ * (FRAMEWORK-BUG-016).
43
+ */
44
+ conclusion?: string | undefined;
32
45
  bucket?: string | undefined;
33
46
  /**
34
47
  * — optional pre-classified hint (test-only seam)
@@ -1 +1 @@
1
- {"version":3,"file":"stop-matrix.d.mts","sourceRoot":"","sources":["../../../src/cli/ci-watch/stop-matrix.mjs"],"names":[],"mappings":"AA8BA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wDAHW,aAAa,GACX,cAAc,CA8I1B;AAED,+CAGC;;;;;;;;;;;;;;;;;;;YA7Ja,QAAQ,EAAE;;;;WACV,MAAM;;;;gBACN,MAAM;;;YAGN,MAAM;0BACN,OAAO;YACP,MAAM"}
1
+ {"version":3,"file":"stop-matrix.d.mts","sourceRoot":"","sources":["../../../src/cli/ci-watch/stop-matrix.mjs"],"names":[],"mappings":"AAoCA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wDAHW,aAAa,GACX,cAAc,CAuJ1B;AAED,+CAuBC;;;;;;;;;;;;;;;;;;;;;;;;;;;YA1La,QAAQ,EAAE;;;;WACV,MAAM;;;;gBACN,MAAM;;;YAGN,MAAM;0BACN,OAAO;YACP,MAAM"}
@@ -5,11 +5,17 @@
5
5
  * { action, counts_toward_budget, reason }
6
6
  *
7
7
  * No I/O, no fs, no gh CLI shell-outs — caller pre-fetches `checks` (the
8
- * payload from `gh pr checks --json name,bucket,state,conclusion,detailsUrl`)
8
+ * payload from `gh pr checks --json name,bucket,state,link,workflow,completedAt`)
9
9
  * and `prMetadata` (`gh pr view --json state,reviewDecision`) and passes them
10
10
  * in. The orchestrator in commands/ci-watch.mjs handles the side effects
11
11
  * implied by the action.
12
12
  *
13
+ * Primary per-check signal is `state` (uppercase per gh-CLI: SUCCESS,
14
+ * FAILURE, CANCELLED, TIMED_OUT, SKIPPED, IN_PROGRESS, QUEUED, PENDING,
15
+ * NEUTRAL). The legacy lowercase per-check field that gh dropped circa
16
+ * 2.65 is kept as a soft fallback for in-memory test fixtures only —
17
+ * production gh payloads no longer expose it (FRAMEWORK-BUG-016).
18
+ *
13
19
  * Predicate order matters — short-circuits go first (merged/closed/green/cap
14
20
  * fire before any per-check inspection). See spec.md §9 table for the full
15
21
  * ordering.
@@ -28,8 +34,13 @@ const INFRA_FAILURE_CLASSES = new Set(['runner-oom', 'runner-cancelled', 'gh-5xx
28
34
  /**
29
35
  * @typedef {Object} CheckRow
30
36
  * @property {string} [name]
31
- * @property {string} [conclusion] e.g. 'success' | 'failure' | 'cancelled' | 'timed_out' | 'neutral'
32
- * @property {string} [state]
37
+ * @property {string} [state] Primary per-check status (gh-CLI uppercase enum):
38
+ * 'SUCCESS' | 'FAILURE' | 'CANCELLED' | 'TIMED_OUT' | 'SKIPPED' |
39
+ * 'IN_PROGRESS' | 'QUEUED' | 'PENDING' | 'NEUTRAL'.
40
+ * @property {string} [conclusion] Legacy lowercase per-check field
41
+ * (deprecated — gh dropped it from `pr checks --json` schema circa 2.65).
42
+ * Retained only as a soft fallback for in-memory test fixtures
43
+ * (FRAMEWORK-BUG-016).
33
44
  * @property {string} [bucket]
34
45
  * @property {string} [failure_class] — optional pre-classified hint (test-only seam)
35
46
  * @property {string} [log_fingerprint] — optional digest for identical-log detection
@@ -60,7 +71,15 @@ export function classify({ checks, state, prMetadata }) {
60
71
  return { action: 'closed', counts_toward_budget: false, reason: 'pr is closed unmerged' };
61
72
  }
62
73
  const failed = rows.filter((c) => isFailing(c));
63
- const allGreen = rows.length > 0 && rows.every((c) => c.conclusion === 'success');
74
+ // FRAMEWORK-BUG-016: `state` is gh-CLI's primary per-check field post-2.65.
75
+ // SKIPPED rows (workflow `if:` guards that didn't trip) are
76
+ // success-equivalent — they intentionally did not run and that is OK.
77
+ // Soft fallback to the legacy lowercase field keeps the in-memory test
78
+ // fixtures green (production gh payloads never hit it).
79
+ const isGreenRow = (c) => c.state === 'SUCCESS' ||
80
+ c.state === 'SKIPPED' ||
81
+ (c.state == null && c.conclusion === 'success');
82
+ const allGreen = rows.length > 0 && rows.every(isGreenRow);
64
83
  // 3. All checks green.
65
84
  if (allGreen) {
66
85
  return { action: 'green', counts_toward_budget: false, reason: 'all checks succeeded' };
@@ -174,6 +193,24 @@ export function classify({ checks, state, prMetadata }) {
174
193
  };
175
194
  }
176
195
  export function isFailing(check) {
196
+ // FRAMEWORK-BUG-016 / BUG-025: prefer the uppercase `state` field that gh
197
+ // 2.65+ exposes. SKIPPED / NEUTRAL / IN_PROGRESS / QUEUED / PENDING /
198
+ // SUCCESS are explicitly NOT red — SKIPPED in particular must never
199
+ // burn a fix attempt (workflow `if:` guards routinely skip jobs on PRs
200
+ // that don't trip them). The legacy lowercase field is a soft fallback
201
+ // for in-memory test fixtures only.
202
+ const s = check.state;
203
+ if (s === 'SUCCESS' ||
204
+ s === 'SKIPPED' ||
205
+ s === 'NEUTRAL' ||
206
+ s === 'IN_PROGRESS' ||
207
+ s === 'QUEUED' ||
208
+ s === 'PENDING') {
209
+ return false;
210
+ }
211
+ if (s === 'FAILURE' || s === 'CANCELLED' || s === 'TIMED_OUT') {
212
+ return true;
213
+ }
177
214
  const c = check.conclusion;
178
215
  return c === 'failure' || c === 'cancelled' || c === 'timed_out' || c === 'action_required';
179
216
  }
@@ -1 +1 @@
1
- {"version":3,"file":"stop-matrix.mjs","sourceRoot":"","sources":["../../../src/cli/ci-watch/stop-matrix.mjs"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"stop-matrix.mjs","sourceRoot":"","sources":["../../../src/cli/ci-watch/stop-matrix.mjs"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export function cmdFanOut(subcommand: any, projectDir: any, extraArgs: any): import("../util/result.mjs").CommandResult;
2
+ //# sourceMappingURL=fan-out.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fan-out.d.mts","sourceRoot":"","sources":["../../../src/cli/commands/fan-out.mjs"],"names":[],"mappings":"AAmEA,wHA6DC"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * commands/fan-out.mjs — FRAMEWORK-BUG-033 capability gap (ID-04 / ID-05).
3
+ *
4
+ * Single resolver helper for the `/apt:fan-out` SKILL body. Three input
5
+ * forms accepted (`--tasks`, `--phases`, `--unblocked`); all resolve to
6
+ * the canonical `{task_ids, source, batch_id}` shape. The actual
7
+ * orchestration loop (worktree create, worker spawn, polling) lives in
8
+ * the SKILL body — this CLI just materializes the batch.
9
+ *
10
+ * Subcommands: `resolve`.
11
+ */
12
+ import { existsSync, readFileSync } from 'node:fs';
13
+ import { join, resolve } from 'node:path';
14
+ import { parseFlags } from '../util/args.mjs';
15
+ import { err, ok } from '../util/result.mjs';
16
+ const ID_PATTERN = /^[a-z0-9_-]+$/i;
17
+ function generateBatchId() {
18
+ const d = new Date();
19
+ const yyyy = d.getUTCFullYear();
20
+ const mm = String(d.getUTCMonth() + 1).padStart(2, '0');
21
+ const dd = String(d.getUTCDate()).padStart(2, '0');
22
+ const hex = Math.random().toString(16).slice(2, 8);
23
+ return `fanout-${yyyy}${mm}${dd}-${hex}`;
24
+ }
25
+ function parseIdList(raw) {
26
+ if (typeof raw !== 'string')
27
+ return [];
28
+ return raw
29
+ .split(',')
30
+ .map((s) => s.trim())
31
+ .filter(Boolean);
32
+ }
33
+ function readState(targetDir) {
34
+ const statePath = join(targetDir, '.aperant', 'state.json');
35
+ if (!existsSync(statePath))
36
+ return null;
37
+ try {
38
+ return JSON.parse(readFileSync(statePath, 'utf-8'));
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ function resolveFromPhases(state, phases) {
45
+ const wanted = new Set(phases);
46
+ const ids = [];
47
+ for (const [id, task] of Object.entries(state?.active_tasks || {})) {
48
+ if (!task)
49
+ continue;
50
+ const key = `${task.milestone_id ?? ''}/${task.phase_id ?? ''}`;
51
+ if (wanted.has(key) && task.lifecycle_phase !== 'closed') {
52
+ ids.push(id);
53
+ }
54
+ }
55
+ return ids;
56
+ }
57
+ function resolveUnblocked(state) {
58
+ const ids = [];
59
+ for (const [id, task] of Object.entries(state?.active_tasks || {})) {
60
+ if (task?.lifecycle_phase === 'ready-for-agent')
61
+ ids.push(id);
62
+ }
63
+ return ids;
64
+ }
65
+ export function cmdFanOut(subcommand, projectDir, extraArgs) {
66
+ if (!subcommand) {
67
+ return err('Usage: apt-tools fan-out <resolve> <project-dir> [--tasks <ids> | --phases <m/p,m/p> | --unblocked]');
68
+ }
69
+ switch (subcommand) {
70
+ case 'resolve': {
71
+ if (!projectDir) {
72
+ return err('Usage: apt-tools fan-out resolve <project-dir> [--tasks <ids> | --phases <m/p,m/p> | --unblocked]');
73
+ }
74
+ const targetDir = resolve(projectDir);
75
+ const flags = parseFlags(extraArgs);
76
+ const hasTasks = flags.has('tasks');
77
+ const hasPhases = flags.has('phases');
78
+ const hasUnblocked = flags.has('unblocked');
79
+ const inputForms = [hasTasks, hasPhases, hasUnblocked].filter(Boolean).length;
80
+ if (inputForms === 0) {
81
+ return err('One of --tasks, --phases, or --unblocked is required');
82
+ }
83
+ if (inputForms > 1) {
84
+ return err('Only one of --tasks, --phases, or --unblocked may be passed at a time');
85
+ }
86
+ let taskIds = [];
87
+ let source = '';
88
+ if (hasTasks) {
89
+ taskIds = parseIdList(flags.get('tasks'));
90
+ source = 'tasks';
91
+ for (const id of taskIds) {
92
+ if (!ID_PATTERN.test(id)) {
93
+ return err(`Invalid task id in --tasks: "${id}" (must match /^[a-z0-9_-]+$/i)`);
94
+ }
95
+ }
96
+ }
97
+ else if (hasPhases) {
98
+ const state = readState(targetDir);
99
+ if (!state)
100
+ return err('No state.json found — cannot resolve --phases');
101
+ taskIds = resolveFromPhases(state, parseIdList(flags.get('phases')));
102
+ source = 'phases';
103
+ }
104
+ else {
105
+ const state = readState(targetDir);
106
+ if (!state)
107
+ return err('No state.json found — cannot resolve --unblocked');
108
+ taskIds = resolveUnblocked(state);
109
+ source = 'unblocked';
110
+ }
111
+ return ok({
112
+ status: 'ok',
113
+ command: 'fan-out-resolve',
114
+ source,
115
+ task_ids: taskIds,
116
+ batch_id: generateBatchId(),
117
+ });
118
+ }
119
+ default:
120
+ return err(`Unknown fan-out subcommand: ${subcommand}. Available: resolve`);
121
+ }
122
+ }
123
+ //# sourceMappingURL=fan-out.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fan-out.mjs","sourceRoot":"","sources":["../../../src/cli/commands/fan-out.mjs"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.mts","sourceRoot":"","sources":["../../../src/cli/commands/init.mjs"],"names":[],"mappings":"AA0EA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wCAHW,MAAM,EAAE,GACN;IAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,iBAAiB,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,GAAC,IAAI,CAAC;IAAC,WAAW,EAAE,OAAO,CAAC;IAAC,aAAa,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,GAAC,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,GAAC,CAAC,GAAC,CAAC,GAAC,CAAC,CAAC;IAAC,mBAAmB,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,OAAO,CAAC;IAAC,aAAa,EAAE,OAAO,CAAC;IAAC,qBAAqB,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAmE1S;AA+wBD;;;;;;;GAOG;AACH,8CAHW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,WACnB,cAAc,0BAoDxB;AAoED;;;;;;GAMG;AACH,oCAJW,MAAM,cACN,MAAM,EAAE,GACN,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAgM3D;;;;;cA9kCa,MAAM,EAAE;;;;qBACR,CAAC,GAAC,CAAC,GAAC,CAAC,GAAC,CAAC;;;;gBACP,MAAM,GAAC,MAAM;;;;4BACb,MAAM,EAAE;;;;aACR,OAAO;;;;mBACP,OAAO;eACP,OAAO,0BAA0B,EAAE,eAAe;;;;;;;;;aAErC,MAAM;eAAS,OAAO;cAAQ,MAAM;iBAAW,MAAM;;;;;;aAErD,MAAM;eAAS,OAAO;gBAAU,MAAM;;;;;;kBAEvC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;0BAAoB,OAAO,EAAE;2BAAqB,OAAO,EAAE;oBAAc,OAAO,EAAE"}
1
+ {"version":3,"file":"init.d.mts","sourceRoot":"","sources":["../../../src/cli/commands/init.mjs"],"names":[],"mappings":"AA4EA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wCAHW,MAAM,EAAE,GACN;IAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,iBAAiB,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,GAAC,IAAI,CAAC;IAAC,WAAW,EAAE,OAAO,CAAC;IAAC,aAAa,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,GAAC,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,GAAC,CAAC,GAAC,CAAC,GAAC,CAAC,CAAC;IAAC,mBAAmB,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,OAAO,CAAC;IAAC,aAAa,EAAE,OAAO,CAAC;IAAC,qBAAqB,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAmE1S;AAwzBD;;;;;;;GAOG;AACH,8CAHW,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,WACnB,cAAc,0BAoDxB;AAoED;;;;;;GAMG;AACH,oCAJW,MAAM,cACN,MAAM,EAAE,GACN,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA4L3D;;;;;cAnnCa,MAAM,EAAE;;;;qBACR,CAAC,GAAC,CAAC,GAAC,CAAC,GAAC,CAAC;;;;gBACP,MAAM,GAAC,MAAM;;;;4BACb,MAAM,EAAE;;;;aACR,OAAO;;;;mBACP,OAAO;eACP,OAAO,0BAA0B,EAAE,eAAe;;;;;;;;;aAErC,MAAM;eAAS,OAAO;cAAQ,MAAM;iBAAW,MAAM;;;;;;aAErD,MAAM;eAAS,OAAO;gBAAU,MAAM;;;;;;kBAEvC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;0BAAoB,OAAO,EAAE;2BAAqB,OAAO,EAAE;oBAAc,OAAO,EAAE"}
@@ -3,8 +3,9 @@
3
3
  * rewrite, 2026-04).
4
4
  *
5
5
  * Replaces the previous Claude-only `cp -r` installer with the per-runtime
6
- * transform pipeline (src/cli/install/pipeline.mjs). All 14 runtime targets
7
- * supported via `--<runtime>` flags or `--all`. Default with no flags in
6
+ * transform pipeline (src/cli/install/pipeline.mjs). Registered runtime
7
+ * targets are supported via `--<runtime>` flags or `--all`. Default with no
8
+ * flags in
8
9
  * non-TTY mode → `--claude` (backward compat — existing skills invoke
9
10
  * `apt-tools init <dir>` with no flags). Default in TTY → interactive prompt
10
11
  * with auto-detection markers.
@@ -28,8 +29,9 @@ import { buildAperantGitignoreTemplate } from '../config/upgrade-gitignore.mjs';
28
29
  import { detectPublicRemote } from '../git/remote.mjs';
29
30
  import { detectArchetype } from '../install/archetype.mjs';
30
31
  import { installKernel } from '../install/install-kernel.mjs';
32
+ import { MANIFEST_FILENAME } from '../install/manifest.mjs';
31
33
  import { discoverCanonicalFiles, runInstall, writeLastInstallSummary, } from '../install/pipeline.mjs';
32
- import { RUNTIMES, suggestRuntimes } from '../install/runtime-detect.mjs';
34
+ import { getRuntime, RUNTIMES, suggestRuntimes } from '../install/runtime-detect.mjs';
33
35
  import { runLegacyCleanup } from '../install/runtime-migrate.mjs';
34
36
  import { CLASS_A_SETTINGS, CLASS_B_SETTINGS, findClassDescriptor, parseClassFlags, } from '../install/setting-class.mjs';
35
37
  import { detectAgentRuntime } from '../util/agent-runtime.mjs';
@@ -350,6 +352,44 @@ function selectRuntimes(targetDir, parsedFlags) {
350
352
  }
351
353
  return [...suggested];
352
354
  }
355
+ /**
356
+ * Return the runtime ids that should be installed during a no-schema-drift
357
+ * init. A current config is not enough to prove runtime files exist on this
358
+ * machine: team repos often carry `.aperant/config.json` but not local
359
+ * runtime roots/manifests.
360
+ *
361
+ * @param {string} targetDir
362
+ * @param {string[]} runtimeIds
363
+ * @returns {string[]}
364
+ */
365
+ function runtimesMissingManifest(targetDir, runtimeIds) {
366
+ const missing = [];
367
+ for (const id of runtimeIds) {
368
+ const runtime = getRuntime(id);
369
+ if (!runtime)
370
+ continue;
371
+ const manifestPath = join(targetDir, runtime.installRoot, MANIFEST_FILENAME);
372
+ if (!existsSync(manifestPath))
373
+ missing.push(id);
374
+ }
375
+ return missing;
376
+ }
377
+ /**
378
+ * Current-schema init should still run the runtime installer when the user
379
+ * explicitly requested runtimes, when --force-runtimes is present, or when
380
+ * local runtime roots are suggested but have no apt manifest.
381
+ *
382
+ * @param {string} targetDir
383
+ * @param {ReturnType<typeof parseInstallFlags>} parsedFlags
384
+ * @returns {string[]}
385
+ */
386
+ function selectNoDriftRuntimeInstallIds(targetDir, parsedFlags) {
387
+ if (parsedFlags.forceRuntimes)
388
+ return selectRuntimes(targetDir, parsedFlags);
389
+ if (parsedFlags.runtimes_explicit)
390
+ return parsedFlags.runtimes;
391
+ return runtimesMissingManifest(targetDir, suggestRuntimes(targetDir));
392
+ }
353
393
  /**
354
394
  * Non-TTY / scripted path — derive answers from flags + sensible defaults,
355
395
  * gated by the Class A/B classifier (spec R2 + R3).
@@ -600,11 +640,16 @@ async function interactiveInterview(targetDir, version, canonicalRoot, archetype
600
640
  migration.applied_defaults.length === 0 &&
601
641
  migration.auto_added.length === 0;
602
642
  if (noDrift) {
603
- // 0.6.7 (Bug 2 fix) — `--force-runtimes` bypasses the TTY
604
- // "Nothing to do." outro so the runtime walker re-stamps every
605
- // manifest; cmdInit's force-runtimes synthesis path takes over.
606
- if (parsedFlags.forceRuntimes) {
607
- return { upgrade_noop: true, skipBootstrap, migration };
643
+ const runtimeInstallIds = selectNoDriftRuntimeInstallIds(targetDir, parsedFlags);
644
+ // Runtime repair bypasses the TTY "Nothing to do." outro so the
645
+ // runtime walker can re-stamp missing manifests first.
646
+ if (runtimeInstallIds.length > 0) {
647
+ return {
648
+ upgrade_noop: true,
649
+ skipBootstrap,
650
+ migration,
651
+ runtime_reinstall_runtimes: runtimeInstallIds,
652
+ };
608
653
  }
609
654
  process.stdout.write(`\n${aperantBanner(version)}\n\n`);
610
655
  clack.intro('install');
@@ -1058,20 +1103,19 @@ export async function cmdInit(projectDir, extraArgs = []) {
1058
1103
  rerun_with: answers.rerun_with,
1059
1104
  }, 2);
1060
1105
  }
1061
- // Upgrade no-drift — short-circuit before installRuntimes.
1062
- // /apt:update is the right surface for refreshing skills on a
1063
- // no-drift run; init's job here is to confirm the schema is current.
1106
+ const noDriftRuntimeInstallIds = answers?.upgrade_noop
1107
+ ? (answers.runtime_reinstall_runtimes ??
1108
+ selectNoDriftRuntimeInstallIds(installTarget, parsedFlags))
1109
+ : [];
1110
+ // Upgrade no-drift with no runtime work — short-circuit before
1111
+ // installRuntimes. /apt:update remains the right surface for refreshing
1112
+ // already-manifested skills; init only repairs selected or suggested
1113
+ // runtime roots that lack manifests.
1064
1114
  // The bootstrap auto-chain still runs (it's deterministic and
1065
1115
  // idempotent — preserves PROJECT.md, refreshes sidecar from it).
1066
1116
  // In TTY mode clack already rendered "Nothing to do." to stdout,
1067
1117
  // so suppress the JSON envelope (matches the cancelled path).
1068
- //
1069
- // 0.6.7 (Bug 2 fix) — `--force-runtimes` bypasses this short-circuit
1070
- // so `/apt:update` can re-stamp every runtime manifest even when
1071
- // `.aperant/config.json` is at the current schema. We synthesize a
1072
- // real InstallAnswers shape from the existing config so
1073
- // installRuntimes + finishInit run their normal upgrade path.
1074
- if (answers?.upgrade_noop && !parsedFlags.forceRuntimes) {
1118
+ if (answers?.upgrade_noop && noDriftRuntimeInstallIds.length === 0) {
1075
1119
  if (interactiveTty)
1076
1120
  return rawText('', 0);
1077
1121
  const bootstrap = answers.skipBootstrap
@@ -1087,18 +1131,14 @@ export async function cmdInit(projectDir, extraArgs = []) {
1087
1131
  message: `Already up to date — v${version}`,
1088
1132
  });
1089
1133
  }
1090
- // 0.6.7 (Bug 2 fix) when forceRuntimes is set on a no-drift run,
1091
- // synthesize a real InstallAnswers shape from the existing config so
1092
- // the runtime walker re-stamps every manifest. Visibility/autonomy/
1093
- // cross-verification are read from answers.migration.migrated (already
1094
- // in memory from nonInteractiveAnswers — avoids a second config read);
1095
- // runtimes resolved via selectRuntimes helper shared with the upgrade path.
1134
+ // Current-schema runtime installsynthesize a real InstallAnswers
1135
+ // shape from the existing config so installRuntimes + finishInit run
1136
+ // their normal upgrade path.
1096
1137
  let effectiveAnswers = answers;
1097
- if (answers?.upgrade_noop && parsedFlags.forceRuntimes) {
1138
+ if (answers?.upgrade_noop && noDriftRuntimeInstallIds.length > 0) {
1098
1139
  const existing = answers.migration?.migrated || {};
1099
- const runtimes = selectRuntimes(installTarget, parsedFlags);
1100
1140
  effectiveAnswers = {
1101
- runtimes,
1141
+ runtimes: noDriftRuntimeInstallIds,
1102
1142
  autonomyDefault: existing?.autonomy?.default ?? 1,
1103
1143
  visibility: existing?.share?.visibility === 'team' ? 'team' : 'solo',
1104
1144
  crossVerificationTools: existing?.multi_model?.cross_verification?.tools ?? [],