@aperant/framework 0.9.0 → 0.11.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.
Files changed (138) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/agents/apt-verifier.md +6 -3
  3. package/dist/cli/commands/catch-up.d.mts +9 -0
  4. package/dist/cli/commands/catch-up.d.mts.map +1 -0
  5. package/dist/cli/commands/catch-up.mjs +64 -0
  6. package/dist/cli/commands/catch-up.mjs.map +1 -0
  7. package/dist/cli/commands/init.d.mts.map +1 -1
  8. package/dist/cli/commands/init.mjs +17 -1
  9. package/dist/cli/commands/init.mjs.map +1 -1
  10. package/dist/cli/commands/pr-review.d.mts +16 -4
  11. package/dist/cli/commands/pr-review.d.mts.map +1 -1
  12. package/dist/cli/commands/pr-review.mjs +30 -11
  13. package/dist/cli/commands/pr-review.mjs.map +1 -1
  14. package/dist/cli/commands/route.d.mts.map +1 -1
  15. package/dist/cli/commands/route.mjs +92 -2
  16. package/dist/cli/commands/route.mjs.map +1 -1
  17. package/dist/cli/commands/validate-evidence.d.mts +1 -1
  18. package/dist/cli/config/share-policy.d.mts.map +1 -1
  19. package/dist/cli/config/share-policy.mjs +1 -0
  20. package/dist/cli/config/share-policy.mjs.map +1 -1
  21. package/dist/cli/config/upgrade-gitignore.d.mts.map +1 -1
  22. package/dist/cli/config/upgrade-gitignore.mjs +1 -0
  23. package/dist/cli/config/upgrade-gitignore.mjs.map +1 -1
  24. package/dist/cli/consistency/parse-review.mjs +5 -3
  25. package/dist/cli/consistency/parse-review.mjs.map +1 -1
  26. package/dist/cli/consistency/rules/r5-verdict-consistency.d.mts.map +1 -1
  27. package/dist/cli/consistency/rules/r5-verdict-consistency.mjs +10 -8
  28. package/dist/cli/consistency/rules/r5-verdict-consistency.mjs.map +1 -1
  29. package/dist/cli/coordination/catch-up.d.mts +26 -0
  30. package/dist/cli/coordination/catch-up.d.mts.map +1 -0
  31. package/dist/cli/coordination/catch-up.mjs +239 -0
  32. package/dist/cli/coordination/catch-up.mjs.map +1 -0
  33. package/dist/cli/coordination/last-seen.d.mts +45 -0
  34. package/dist/cli/coordination/last-seen.d.mts.map +1 -0
  35. package/dist/cli/coordination/last-seen.mjs +128 -0
  36. package/dist/cli/coordination/last-seen.mjs.map +1 -0
  37. package/dist/cli/coordination/store.d.mts +15 -0
  38. package/dist/cli/coordination/store.d.mts.map +1 -1
  39. package/dist/cli/coordination/store.mjs +16 -0
  40. package/dist/cli/coordination/store.mjs.map +1 -1
  41. package/dist/cli/design/frontmatter-schema.d.mts +8 -8
  42. package/dist/cli/dispatch.d.mts.map +1 -1
  43. package/dist/cli/dispatch.mjs +2 -0
  44. package/dist/cli/dispatch.mjs.map +1 -1
  45. package/dist/cli/gate/gates/review-clean.d.mts.map +1 -1
  46. package/dist/cli/gate/gates/review-clean.mjs +4 -2
  47. package/dist/cli/gate/gates/review-clean.mjs.map +1 -1
  48. package/dist/cli/git/default-branch.d.mts +51 -0
  49. package/dist/cli/git/default-branch.d.mts.map +1 -0
  50. package/dist/cli/git/default-branch.mjs +234 -0
  51. package/dist/cli/git/default-branch.mjs.map +1 -0
  52. package/dist/cli/git/identity.d.mts +3 -5
  53. package/dist/cli/git/identity.d.mts.map +1 -1
  54. package/dist/cli/git/identity.mjs +10 -4
  55. package/dist/cli/git/identity.mjs.map +1 -1
  56. package/dist/cli/install/legacy-paths.d.mts.map +1 -1
  57. package/dist/cli/install/legacy-paths.mjs +2 -0
  58. package/dist/cli/install/legacy-paths.mjs.map +1 -1
  59. package/dist/cli/install/mcp-provision.d.mts +26 -0
  60. package/dist/cli/install/mcp-provision.d.mts.map +1 -0
  61. package/dist/cli/install/mcp-provision.mjs +281 -0
  62. package/dist/cli/install/mcp-provision.mjs.map +1 -0
  63. package/dist/cli/install/mcp-server-specs.d.mts +80 -0
  64. package/dist/cli/install/mcp-server-specs.d.mts.map +1 -0
  65. package/dist/cli/install/mcp-server-specs.mjs +102 -0
  66. package/dist/cli/install/mcp-server-specs.mjs.map +1 -0
  67. package/dist/cli/install/runtime-detect.d.mts +1 -0
  68. package/dist/cli/install/runtime-detect.d.mts.map +1 -1
  69. package/dist/cli/install/runtime-detect.mjs +5 -0
  70. package/dist/cli/install/runtime-detect.mjs.map +1 -1
  71. package/dist/cli/install/toml-merge.d.mts +40 -0
  72. package/dist/cli/install/toml-merge.d.mts.map +1 -0
  73. package/dist/cli/install/toml-merge.mjs +134 -0
  74. package/dist/cli/install/toml-merge.mjs.map +1 -0
  75. package/dist/cli/personas/sidecar.d.mts +1 -1
  76. package/dist/cli/roadmap/lifecycle.d.mts +1 -1
  77. package/dist/cli/roadmap/rollup.d.mts +2 -2
  78. package/dist/cli/util/events-path.d.mts +8 -0
  79. package/dist/cli/util/events-path.d.mts.map +1 -0
  80. package/dist/cli/util/events-path.mjs +23 -0
  81. package/dist/cli/util/events-path.mjs.map +1 -0
  82. package/dist/cli/util/runtime-capabilities.d.mts +30 -0
  83. package/dist/cli/util/runtime-capabilities.d.mts.map +1 -0
  84. package/dist/cli/util/runtime-capabilities.mjs +214 -0
  85. package/dist/cli/util/runtime-capabilities.mjs.map +1 -0
  86. package/dist/cli/verify-proof/audit.d.mts +2 -7
  87. package/dist/cli/verify-proof/audit.d.mts.map +1 -1
  88. package/dist/cli/verify-proof/audit.mjs +11 -17
  89. package/dist/cli/verify-proof/audit.mjs.map +1 -1
  90. package/dist/cli/verify-proof/runtime-detect.d.mts +2 -29
  91. package/dist/cli/verify-proof/runtime-detect.d.mts.map +1 -1
  92. package/dist/cli/verify-proof/runtime-detect.mjs +8 -203
  93. package/dist/cli/verify-proof/runtime-detect.mjs.map +1 -1
  94. package/dist/plugin/.claude-plugin/plugin.json +2 -1
  95. package/dist/plugin/agents/apt-verifier.md +6 -3
  96. package/dist/plugin/skills/apt/SKILL.md +29 -0
  97. package/dist/plugin/skills/apt-catch-up/SKILL.md +79 -0
  98. package/dist/plugin/skills/apt-pr-review/SKILL.md +59 -4
  99. package/dist/plugin/skills/apt-setup/SKILL.md +76 -0
  100. package/dist/plugin/skills/apt-verify/SKILL.md +1 -1
  101. package/dist/plugin/skills/apt-verify-proof/SKILL.md +25 -1
  102. package/drivers/browser/skill/agent-browser/SKILL.md +51 -0
  103. package/drivers/tauri/README.md +107 -0
  104. package/drivers/tauri/driver.mjs +88 -0
  105. package/drivers/tauri/manifest.json +37 -0
  106. package/drivers/tauri/scripts/tauri-mcp.sh +41 -0
  107. package/package.json +138 -138
  108. package/skills/apt/SKILL.md +29 -0
  109. package/skills/apt-catch-up/SKILL.md +79 -0
  110. package/skills/apt-pr-review/SKILL.md +59 -4
  111. package/skills/apt-setup/SKILL.md +76 -0
  112. package/skills/apt-verify/SKILL.md +1 -1
  113. package/skills/apt-verify-proof/SKILL.md +25 -1
  114. package/src/cli/commands/catch-up.mjs +67 -0
  115. package/src/cli/commands/init.mjs +19 -0
  116. package/src/cli/commands/pr-review.mjs +32 -11
  117. package/src/cli/commands/route.mjs +92 -1
  118. package/src/cli/config/share-policy.mjs +1 -0
  119. package/src/cli/config/upgrade-gitignore.mjs +1 -0
  120. package/src/cli/consistency/parse-review.mjs +5 -3
  121. package/src/cli/consistency/rules/r5-verdict-consistency.mjs +14 -9
  122. package/src/cli/coordination/catch-up.mjs +231 -0
  123. package/src/cli/coordination/last-seen.mjs +131 -0
  124. package/src/cli/coordination/store.mjs +18 -0
  125. package/src/cli/dispatch.mjs +2 -0
  126. package/src/cli/gate/gates/review-clean.mjs +4 -2
  127. package/src/cli/git/default-branch.mjs +250 -0
  128. package/src/cli/git/identity.mjs +9 -3
  129. package/src/cli/install/legacy-paths.mjs +2 -0
  130. package/src/cli/install/mcp-provision.mjs +293 -0
  131. package/src/cli/install/mcp-server-specs.mjs +101 -0
  132. package/src/cli/install/runtime-detect.mjs +9 -0
  133. package/src/cli/install/toml-merge.mjs +139 -0
  134. package/src/cli/util/events-path.mjs +24 -0
  135. package/src/cli/util/runtime-capabilities.mjs +216 -0
  136. package/src/cli/verify-proof/audit.mjs +11 -17
  137. package/src/cli/verify-proof/runtime-detect.mjs +11 -204
  138. package/templates/config.json +13 -1
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: apt:catch-up
3
+ description: "Re-view the catch-up panel (Welcome-Back + Team-Change Digest) on demand"
4
+ apt-skill-version: {{APT_VERSION}}
5
+ stage: resume
6
+ intent: capture
7
+ when_to_use: "The user dismissed the auto catch-up panel on their last /apt and wants to re-summon it — an idempotent re-view of where they left off plus what teammates merged. Distinct from /apt:resume (full context restore) and never advances the Last-Seen Marker."
8
+ user_invocable: true
9
+ internal: false
10
+ spawns_agent: false
11
+ agent_name: null
12
+ task_context: none
13
+ default_execution_mode: auto
14
+ execution_modes:
15
+ - auto
16
+ allowed-tools: "Bash, Read"
17
+ argument-hint: "apt:catch-up"
18
+ gates: []
19
+ ---
20
+ <objective>
21
+ Recompute and display the combined catch-up panel — the **Welcome-Back
22
+ Summary** (your most-recently-active task, phase, next step) and the
23
+ **Team-Change Digest** (the PRs teammates merged to main since you last
24
+ pulled) — exactly as the auto-on-apt display would show it, but **without
25
+ advancing the Last-Seen Marker**. This is a read-only re-view: a user who
26
+ dismissed the auto panel can re-summon it without disturbing the baseline and
27
+ without invoking the heavier `/apt:resume`.
28
+ </objective>
29
+
30
+ <your_environment>
31
+ - **Working directory:** The project root (where you were invoked)
32
+ - **apt-tools:** `node packages/framework/bin/apt-tools.mjs` or the installed `apt-tools` binary
33
+ - **Last-Seen Marker:** `.aperant/last-seen/{email-slug}.json` (gitignored, per-machine) — this skill READS it but NEVER writes it.
34
+ </your_environment>
35
+
36
+ <process>
37
+
38
+ ## 1. Recompute the panel (read-only)
39
+
40
+ ```bash
41
+ node packages/framework/bin/apt-tools.mjs catch-up .
42
+ ```
43
+
44
+ This computes the SAME panel the interactive route call produces, against the
45
+ **current** marker — and deliberately does **NOT** call `advanceLastSeen`
46
+ (recompute-only contract). Parse the JSON `catch_up` field from the envelope.
47
+
48
+ ## 2. Render the panel
49
+
50
+ Render exactly as the `apt/SKILL.md` "Catch-up panel" block does — Welcome-Back
51
+ half first, then the Team-Change Digest half. Omit a half whose field is null.
52
+
53
+ If `catch_up` is null, tell the user there is nothing new since their last
54
+ visit (no active stale task and no new merges on main).
55
+
56
+ ```
57
+ Where you left off: {description} — {lifecycle_phase} · subtask {current_subtask} ({relative_time})
58
+ Next: {next_step}
59
+ (+{others_count} others · /apt:resume)
60
+
61
+ While you were away:
62
+ #{number} {title} — {author}
63
+ +{more_count} more on main
64
+ ```
65
+
66
+ </process>
67
+
68
+ <notes>
69
+ - **Never advances the marker.** Unlike the auto-on-apt display (which advances
70
+ the Last-Seen Marker after the panel is evaluated), this command is purely a
71
+ re-view. Re-running it is idempotent.
72
+ - **Distinct from `/apt:resume`.** Resume restores full context and routes to
73
+ the next action; catch-up is a lightweight orientation re-view.
74
+ - **v1 is lazy compute.** The panel is computed on demand here, bounded by the
75
+ commits since your last pull. A background pre-warm worker (like the
76
+ update-check worker) is a tracked follow-up (LD-12), not part of v1.
77
+ - **Vocabulary.** This is the Welcome-Back Summary + Team-Change Digest — not an
78
+ "activity feed", "changelog", or "notifications".
79
+ </notes>
@@ -247,10 +247,11 @@ The command resolves in this precedence order:
247
247
 
248
248
  1. `config.json:pr_review.authorship_overrides['pr-${PR_NUMBER}']` (per-PR pin)
249
249
  2. `config.json:pr_review.authorship_override` (global: `owned` | `external` | `auto`)
250
- 3. `gh pr view` → `authorAssociation` + `headRepository.owner.login` vs. viewer login
251
- - `authorAssociation IN (OWNER, MEMBER, COLLABORATOR)` AND same-repo branch → **owned**
252
- - Fork OR `authorAssociation IN (NONE, CONTRIBUTOR, FIRST_TIME_CONTRIBUTOR)` → **external**
253
- 4. Offline / gh unavailable / ambiguous payload → **unknown**
250
+ 3. `gh pr view` → `gh.author.login` vs. viewer login (G44 — `owned` means a PR you **authored**, not a branch you can push to)
251
+ - Same-repo branch AND `gh.author.login === viewer login` → **owned**
252
+ - Any other same-repo PR (a teammate's, or a repo-`OWNER` viewing someone else's PR) → **external** (`authorAssociation` is no longer consulted for the `owned` decision — it is retained only in the human-readable signal)
253
+ - Fork (head-repo owner base-repo owner) → **external**
254
+ 4. Offline / gh unavailable / ambiguous payload → **unknown** (treated as `external` for safety, see below)
254
255
 
255
256
  When the result is `unknown`, treat it as `external` for safety and prompt the user:
256
257
 
@@ -469,6 +470,55 @@ fi
469
470
 
470
471
  After this point, the per-round artifact root is `.aperant/pr-reviews/pr-${PR_NUMBER}/rounds/${ROUND}/` (the `${REVIEW_ID}` is still used for the git worktree branch and temporary files). Subsequent `git diff` invocations within the round use `${PINNED_HEAD_SHA}` as the head ref.
471
472
 
473
+ ### Step 4c: Mergeability Pre-Check (PR reviews only)
474
+
475
+ **Why this exists.** The review diff is `git diff {BASE}...{HEAD}` (three-dot — from the *merge-base*), so it shows the PR's own changes relative to where it branched. It is **blind to drift on `{BASE}` since then** and therefore says nothing about whether the PR still merges cleanly. A PR can pass a glowing review and be unmergeable. This step closes that gap: it is **informational, never blocking** (the review still runs in full), but a conflicting/stale state MUST be surfaced so the reader doesn't trust a review of a diff that no longer applies.
476
+
477
+ ```bash
478
+ # PR-mode only. Query GitHub's authoritative mergeability + measure drift.
479
+ if [ -n "$PR_NUMBER" ]; then
480
+ MERGE_JSON=$(gh pr view ${PR_NUMBER} --json mergeable,mergeStateStatus,baseRefName 2>/dev/null)
481
+ MERGEABLE=$(echo "$MERGE_JSON" | jq -r '.mergeable // "UNKNOWN"')
482
+ MERGE_STATE=$(echo "$MERGE_JSON" | jq -r '.mergeStateStatus // "UNKNOWN"')
483
+ BASE_REF=$(echo "$MERGE_JSON" | jq -r '.baseRefName // "main"')
484
+ git fetch origin "${BASE_REF}" 2>/dev/null
485
+ BEHIND=$(git rev-list --count ${PINNED_HEAD_SHA}..origin/${BASE_REF} 2>/dev/null || echo "?")
486
+ AHEAD=$(git rev-list --count origin/${BASE_REF}..${PINNED_HEAD_SHA} 2>/dev/null || echo "?")
487
+
488
+ # If GitHub reports a conflict (or hasn't computed yet), enumerate the conflicting
489
+ # files locally without touching any working tree (git >= 2.38):
490
+ CONFLICT_FILES=""
491
+ if [ "$MERGEABLE" = "CONFLICTING" ] || [ "$MERGE_STATE" = "DIRTY" ]; then
492
+ CONFLICT_FILES=$(git merge-tree --write-tree --name-only origin/${BASE_REF} ${PINNED_HEAD_SHA} 2>/dev/null \
493
+ | tail -n +2 | sed '/^$/d' | grep '/' )
494
+ fi
495
+ fi
496
+ ```
497
+
498
+ Record the result in `metadata.json` under a `mergeability` key:
499
+
500
+ ```json
501
+ {
502
+ "mergeability": {
503
+ "mergeable": "CONFLICTING | MERGEABLE | UNKNOWN",
504
+ "merge_state_status": "DIRTY | CLEAN | BLOCKED | BEHIND | ...",
505
+ "base_ref": "main",
506
+ "commits_behind_base": 101,
507
+ "commits_ahead_base": 91,
508
+ "conflicting_files": ["apps/.../App.tsx", "..."]
509
+ }
510
+ }
511
+ ```
512
+
513
+ **How to use the result:**
514
+
515
+ - `MERGEABLE` ∈ {`CONFLICTING`} OR `MERGE_STATE` = `DIRTY` → the PR has conflicts. Surface a **⚠️ Mergeability** block in the Phase 5 intermediate report AND the Phase 8 final report, listing `commits_behind_base` and the `conflicting_files`. In **external / comment-only** mode, include the same block in the Phase 6b comment. State plainly that the branch needs a rebase/merge from `{BASE}` and that **the reviewed diff may be stale** with respect to current `{BASE}`.
516
+ - `MERGE_STATE` = `BEHIND` (mergeable but behind) → note the drift (`commits_behind_base`) as a softer advisory; no conflict, but the diff predates recent `{BASE}` commits.
517
+ - `MERGEABLE` = `UNKNOWN` (GitHub hasn't finished computing, common right after a push) → record `UNKNOWN`; optionally re-poll once after a few seconds. Do not block.
518
+ - `MERGEABLE` = `MERGEABLE`, `MERGE_STATE` = `CLEAN` → record it and move on silently.
519
+
520
+ This step never changes the review's findings or assessment — a conflicting PR with zero code findings is still `approve` on its own merits. Mergeability is reported as a **separate** signal so the reader knows whether the approval can actually land.
521
+
472
522
  ### Step 5: Read All Changed Files
473
523
 
474
524
  For every file in the changed file list, read it in full using the Read tool **from the worktree**:
@@ -1058,6 +1108,10 @@ Review Synthesis (Before Auto-Fix)
1058
1108
  Auto-Checks: {PASS/FAIL}
1059
1109
  lint: {P/F} | typecheck: {P/F} | tests: {P/F}
1060
1110
 
1111
+ Mergeability (PR reviews — from Step 4c): {MERGEABLE/CONFLICTING/UNKNOWN}
1112
+ {if CONFLICTING/DIRTY: "⚠️ conflicts with {BASE} — {behind} commits behind — needs rebase — reviewed diff may be stale"}
1113
+ {if conflicting_files: list them}
1114
+
1061
1115
  Validated Findings:
1062
1116
  CRITICAL: {N}
1063
1117
  HIGH: {N}
@@ -1630,6 +1684,7 @@ Output the human-readable summary:
1630
1684
 
1631
1685
  Review ID: {REVIEW_ID}
1632
1686
  Assessment: {APPROVE / REQUEST CHANGES / COMMENT}
1687
+ Mergeability: {MERGEABLE / CONFLICTING (N behind {BASE}, needs rebase) / UNKNOWN} # PR reviews — from Step 4c / metadata.json.mergeability
1633
1688
 
1634
1689
  Scores:
1635
1690
  Security: {score}/10
@@ -205,6 +205,7 @@ AskUserQuestion([
205
205
  - "Visibility" → jump to **Step 3e** (Batch 6 only), then skip to **Step 4** and **Step 5**
206
206
  - "Task Tracking" → jump to **Step 3g** (Batch 8 only), then skip to **Step 4** and **Step 5**
207
207
  - "Changelog & Release Notes" → jump to **Step 3h** (Batch 9 only), then skip to **Step 4** and **Step 5**
208
+ - "Collaboration" → jump to **Step 3i** (Batch 10 only), then skip to **Step 4** and **Step 5**
208
209
  - "Everything" → proceed to **Step 2** and run all batches (same as fresh setup). **Legacy-completes-Everything marker write:** if the starting config has no `onboarding` block (legacy unmarked config) and the user completes the full flow, write a full `onboarding` block at Step 5 as `{ completed: true, seeded_from_template: false, seeded_at: <ISO-8601 now> }` to record the explicit onboarding pass.
209
210
  - "Done" → display "Settings unchanged." and exit
210
211
 
@@ -1180,6 +1181,74 @@ The selective-edit pass through Batch 9 MUST respect the §1b dispatch — it do
1180
1181
 
1181
1182
  ---
1182
1183
 
1184
+ ## 3i. Present Settings (Batch 10: Collaboration Catch-Up)
1185
+
1186
+ The **collaboration catch-up** surfaces show on every interactive (TTY) `/apt`
1187
+ call: a **Welcome-Back Summary** (your most-recently-active task + next step,
1188
+ always-on, local-only) and a **Team-Change Digest** (the PRs teammates merged
1189
+ to main since you last pulled — gated to `share.visibility` `team`/`oss`).
1190
+ Written to `.aperant/config.json.collaboration`.
1191
+
1192
+ ```
1193
+ AskUserQuestion([
1194
+ {
1195
+ question: "Show the Welcome-Back Summary when you return after a break?",
1196
+ header: "Welcome-Back",
1197
+ multiSelect: false,
1198
+ options: [
1199
+ { label: "Yes (Recommended)", description: "On your next /apt after the staleness window, surface where you left off — task, phase, next step. Local only." },
1200
+ { label: "No", description: "Never show the welcome-back nudge." }
1201
+ ]
1202
+ },
1203
+ {
1204
+ question: "How many hours away before the Welcome-Back Summary fires?",
1205
+ header: "Threshold (hours)",
1206
+ multiSelect: false,
1207
+ options: [
1208
+ { label: "16 (Recommended)", description: "Roughly 'since yesterday' — fires after an overnight gap." },
1209
+ { label: "8", description: "Fires after a long lunch / half-day gap." },
1210
+ { label: "24", description: "Once-a-day cadence." }
1211
+ ]
1212
+ },
1213
+ {
1214
+ question: "Show the Team-Change Digest (teammates' merged PRs since your last pull)?",
1215
+ header: "Team Digest",
1216
+ multiSelect: false,
1217
+ options: [
1218
+ { label: "Yes (Recommended)", description: "Only computes when share.visibility is team/oss; reads local git history, never fetches." },
1219
+ { label: "No", description: "Never show the digest, even in team/oss mode." }
1220
+ ]
1221
+ },
1222
+ {
1223
+ question: "Maximum PRs to list in the digest before '+N more on main'?",
1224
+ header: "Digest Max Entries",
1225
+ multiSelect: false,
1226
+ options: [
1227
+ { label: "7 (Recommended)", description: "Low-noise ceiling; the rest collapse into a '+N more' tail." },
1228
+ { label: "5", description: "Tighter." },
1229
+ { label: "12", description: "Roomier." }
1230
+ ]
1231
+ }
1232
+ ])
1233
+ ```
1234
+
1235
+ For hosts without structured prompts, present the same four questions as
1236
+ numbered lists (Batch 5 style) and read single-index responses.
1237
+
1238
+ ### 3i.1 Review-Mode routing entry
1239
+
1240
+ The "1b. Review Mode" routing table (§1b) includes "Collaboration" as a
1241
+ selectable edit target — jumping directly back into this Batch 10 picker.
1242
+
1243
+ ### 3i.2 Confirmation row
1244
+
1245
+ Add a row to the Step 8 table:
1246
+ ```
1247
+ | Collaboration | welcome-back {on/off} ({N}h) · team-digest {on/off} (max {M}) |
1248
+ ```
1249
+
1250
+ ---
1251
+
1183
1252
  ## 4. Map Answers to Config
1184
1253
 
1185
1254
  **AUDIT-001 — file-routing contract for per-device fields (UPDATED 0.8.1 for FRAMEWORK-RFC-001):** the
@@ -1295,6 +1364,12 @@ Note: task ids are slug-first since v0.6.6 — the date-format setting controls
1295
1364
  - "Sub-Agents" → `"sub-agents"`
1296
1365
  - "Sequential" → `"sequential"`
1297
1366
 
1367
+ **Collaboration Catch-Up (Batch 10):**
1368
+ - Welcome-Back "Yes" → `collaboration.welcome_back.enabled: true`, "No" → `false`
1369
+ - Threshold → `collaboration.welcome_back.threshold_hours: <8|16|24>`
1370
+ - Team Digest "Yes" → `collaboration.team_digest.enabled: true`, "No" → `false`
1371
+ - Digest Max Entries → `collaboration.team_digest.max_entries: <5|7|12>`
1372
+
1298
1373
  ## 5. Write Config
1299
1374
 
1300
1375
  Write merged config to two files per AUDIT-001 routing (see Step 4 block):
@@ -1402,6 +1477,7 @@ Display:
1402
1477
  | Debate Mode | {Auto / Agent Team / Sub-Agents / Sequential} |
1403
1478
  | Visibility | {solo / team / oss} (overrides if any) |
1404
1479
  | Changelog & Release Notes | dev: {Yes/No}; release-notes: {Yes/No} (audience, persona-filter) |
1480
+ | Collaboration | welcome-back {on/off} ({N}h) · team-digest {on/off} (max {M}) |
1405
1481
  | Saved as Defaults | {Yes/No} |
1406
1482
 
1407
1483
  Config saved to: .aperant/config.json
@@ -60,7 +60,7 @@ Before spawning any verifier subagent or running scoring against acceptance crit
60
60
  </locked_decisions>
61
61
  ```
62
62
 
63
- Verifiers apply this digest when interpreting "completeness" and "correctness" — if a decision explicitly deferred a behavior, its absence is NOT a verification failure.
63
+ Verifiers apply this digest when interpreting "completeness" and "correctness" — if a decision explicitly deferred a behavior AND the deferred finding has severity below critical or major, its absence is NOT a verification failure. A deferred critical or major finding IS a Completeness gap — score accordingly and note it in the issues list.
64
64
  </decision_ledger_inject>
65
65
 
66
66
  <process>
@@ -20,6 +20,7 @@ gates: []
20
20
  config_keys:
21
21
  - verification.open_report
22
22
  - verification.report_formats
23
+ - verification.provision_mcp
23
24
  ---
24
25
 
25
26
  # Proof Verification
@@ -104,13 +105,36 @@ disable the walk entirely (root-only behaviour, backwards compat).
104
105
  The `verification.runtimes[]` override stays the final escape hatch
105
106
  and is now usually unnecessary because the walk runs by default.
106
107
 
107
- ### Bundled drivers (4)
108
+ ### Bundled drivers (5)
108
109
 
109
110
  - **browser** (priority=100, ga) — Next.js / vanilla web frontends.
110
111
  - **electron** (priority=90, ga) — Electron desktop apps.
112
+ - **tauri** (priority=85, ga) — Tauri desktop apps.
111
113
  - **cli** (priority=60, ga) — headless CI, output-shape assertions.
112
114
  - **api** (priority=50, ga) — HTTP / JSON-API regression tests.
113
115
 
116
+ ### Install-time MCP provisioning (verification.provision_mcp)
117
+
118
+ `apt-tools init` (and `/apt:update`, which routes through the same path)
119
+ auto-provisions the MCP transport a driver needs into each SELECTED host
120
+ CLI's config, so verify-proof works at the prompt with no manual MCP
121
+ setup. It detects the project runtime and, per host:
122
+
123
+ - **claude** → JSON-merges the server into the project-root `.mcp.json`
124
+ (`mcpServers.<id>`).
125
+ - **codex** → upserts a `[mcp_servers.<id>]` table into the project-root
126
+ `.codex/config.toml`.
127
+
128
+ Electron and Tauri projects get the matching MCP server; web frontends
129
+ get the browser driver's declared `agent-browser` skill copied into the
130
+ host skill dir (`.claude/skills/` / `.agents/skills/`) — its `agent-browser`
131
+ CLI is a documented one-time prereq, the same class as the
132
+ `electron-mcp-server` / `tauri-plugin-mcp-server` npm packages (see each
133
+ driver's README). All writes are **TOFU/non-clobbering**: a pre-existing
134
+ server id, config table, or skill file is never overwritten. Opt out with
135
+ `verification.provision_mcp: false` (default `true`) — then init/update
136
+ writes no host MCP config and no skill.
137
+
114
138
  ### Driver authors
115
139
 
116
140
  Third-party driver authors should run `apt-tools driver doctor .` before
@@ -0,0 +1,67 @@
1
+ /**
2
+ * commands/catch-up.mjs — read-only re-view of the combined catch-up panel
3
+ * (LD-13). Backs the `/apt:catch-up` skill.
4
+ *
5
+ * Recompute-only contract: this command computes the SAME panel the
6
+ * interactive route call would show (Welcome-Back + gated Team-Change Digest)
7
+ * against the CURRENT Last-Seen Marker, but it NEVER advances the marker. A
8
+ * user who dismissed the auto-on-apt display can re-summon it without
9
+ * disturbing their baseline. Distinct from /apt:resume (full context restore).
10
+ */
11
+
12
+ import { resolve } from 'node:path'
13
+ import { loadProjectConfig } from '../config/load.mjs'
14
+ import { resolveSharePolicy } from '../config/share-policy.mjs'
15
+ import { computeCatchUpPanel } from '../coordination/catch-up.mjs'
16
+ import { createCoordinationStore } from '../coordination/store.mjs'
17
+ import { resolveLocalDefaultBranch } from '../git/default-branch.mjs'
18
+ import { err, ok } from '../util/result.mjs'
19
+
20
+ /**
21
+ * @param {string} projectDir
22
+ * @returns {{ envelope: object, exitCode: number }}
23
+ */
24
+ export function cmdCatchUp(projectDir) {
25
+ if (!projectDir) return err('Usage: apt-tools catch-up <project-dir>')
26
+ const targetDir = resolve(projectDir)
27
+
28
+ // Setup mirrors computeCatchUpForRoute in commands/route.mjs (advances marker).
29
+ const config = loadProjectConfig(targetDir)
30
+ if (!config) {
31
+ return ok({
32
+ status: 'ok',
33
+ command: 'catch-up',
34
+ catch_up: null,
35
+ reason: 'No .aperant/config.json — nothing to catch up on',
36
+ })
37
+ }
38
+
39
+ const store = createCoordinationStore(targetDir)
40
+ const marker = store.readLastSeen()
41
+ const td = config?.collaboration?.team_digest
42
+ const vis = resolveSharePolicy(config).visibility
43
+ const needsDigest = td?.enabled !== false && (vis === 'team' || vis === 'oss')
44
+ const defaultBranch = needsDigest ? resolveLocalDefaultBranch(targetDir) : null
45
+
46
+ let catch_up = null
47
+ try {
48
+ catch_up = computeCatchUpPanel(targetDir, {
49
+ config,
50
+ now: Date.now(),
51
+ marker,
52
+ defaultBranch,
53
+ })
54
+ } catch (e) {
55
+ console.warn('[apt] catch-up:', e?.message ?? e)
56
+ catch_up = null
57
+ }
58
+
59
+ // NOTE: recompute-only — we deliberately do NOT call store.advanceLastSeen.
60
+ // The marker baseline is owned by the interactive route path (route.mjs).
61
+ return ok({
62
+ status: 'ok',
63
+ command: 'catch-up',
64
+ catch_up,
65
+ reason: catch_up ? 'Catch-up panel recomputed' : 'Nothing new since your last visit',
66
+ })
67
+ }
@@ -32,6 +32,7 @@ import { deriveSidecar } from '../bootstrap/product-context-sidecar.mjs'
32
32
  import { assertConfigWriteAllowed } from '../config/guard.mjs'
33
33
  import {
34
34
  AUDIT_001_LOCAL_FIELDS,
35
+ loadMergedProjectConfig,
35
36
  migrateDeferredFieldsToLocalConfig,
36
37
  writeLocalConfigField,
37
38
  } from '../config/load.mjs'
@@ -42,6 +43,7 @@ import { detectPublicRemote } from '../git/remote.mjs'
42
43
  import { detectArchetype } from '../install/archetype.mjs'
43
44
  import { installKernel } from '../install/install-kernel.mjs'
44
45
  import { MANIFEST_FILENAME } from '../install/manifest.mjs'
46
+ import { provisionMcpConfig } from '../install/mcp-provision.mjs'
45
47
  import {
46
48
  discoverCanonicalFiles,
47
49
  runInstall,
@@ -1304,6 +1306,23 @@ export async function cmdInit(projectDir, extraArgs = []) {
1304
1306
  if (envelope?.__earlyExit) {
1305
1307
  return envelope.__earlyExit
1306
1308
  }
1309
+
1310
+ // Auto-provision host MCP-server config + the agent-browser skill for
1311
+ // the detected runtime (C38). Runs on BOTH the fresh install AND the
1312
+ // no-drift /apt:update upgrade path (both reach finishInit). finishInit
1313
+ // has already written .aperant/config.json from the template, so the
1314
+ // merged config carries verification.provision_mcp (default true) — the
1315
+ // opt-out gate. TOFU/non-clobbering; never throws.
1316
+ const mergedConfig = loadMergedProjectConfig(installTarget)
1317
+ const mcpProvision = provisionMcpConfig({
1318
+ targetDir: installTarget,
1319
+ selectedRuntimes: effectiveAnswers.runtimes,
1320
+ config: mergedConfig,
1321
+ frameworkRoot: canonicalRoot,
1322
+ version,
1323
+ })
1324
+ envelope.mcp_provision = mcpProvision
1325
+
1307
1326
  if (interactiveTty) {
1308
1327
  await renderOutro(effectiveAnswers, results, version, kernelResult)
1309
1328
  // TTY mode: envelope was persisted to disk + suppressed from stdout
@@ -727,17 +727,30 @@ function parseGhPrView(raw) {
727
727
  * the viewer's personal handle (e.g. `AndyMik90`). They never match, which
728
728
  * previously caused every same-repo, owned PR to be mis-classified as
729
729
  * `external` and silently disabled auto-fix. The third arg `baseRepoOwner`
730
- * is the source of truth for "is this a fork PR?". `viewerLogin` is kept
731
- * only for backward-compatible signal reporting.
730
+ * is the source of truth for "is this a fork PR?".
731
+ *
732
+ * IMPORTANT (G44): "owned" means "a PR I authored," NOT "a branch in a repo I
733
+ * can push to." The owned test is strictly `gh.author.login === viewerLogin`
734
+ * — author IDENTITY, not `authorAssociation`. Previously a same-repo PR with
735
+ * `authorAssociation ∈ {OWNER, MEMBER, COLLABORATOR}` was classified `owned`,
736
+ * which meant every teammate's same-repo PR triggered the auto-fix-and-push
737
+ * pipeline on their branch. Now `authorAssociation` is retained only in the
738
+ * signal for human-readable tracing; it no longer gates the decision. The
739
+ * "is this mine" comparand is `gh.author?.login`, never `headRepoOwner`
740
+ * (comparing headRepoOwner to viewerLogin was the H1 bug — it never matches
741
+ * on an org-owned repo). Fork detection and author identity are independent:
742
+ * fork detection answers "did this come from a fork?"; author identity
743
+ * answers "did I write it?".
732
744
  *
733
745
  * @param {Record<string, any> | null | undefined} gh parsed `gh pr view` JSON (may have `authorAssociation` grafted in by the REST-fetch path)
734
- * @param {string | null | undefined} viewerLogin personal login from `gh api user` (fallback comparand for fork detection)
735
- * @param {string | null | undefined} [baseRepoOwner] the org/user that owns the repo the PR targets (preferred comparand)
746
+ * @param {string | null | undefined} viewerLogin personal login from `gh api user` (compared to `gh.author.login` for the owned test)
747
+ * @param {string | null | undefined} [baseRepoOwner] the org/user that owns the repo the PR targets (fork-detection comparand)
736
748
  * @returns {{ authorship_mode: 'owned' | 'external' | 'unknown', authorship_signal: string }}
737
749
  */
738
750
  export function decideAuthorshipFromGh(gh, viewerLogin, baseRepoOwner = null) {
739
751
  if (!gh) return { authorship_mode: 'unknown', authorship_signal: 'gh_payload_missing' }
740
752
  const association = gh.authorAssociation ?? gh.author?.association ?? null
753
+ const author = gh.author?.login ?? null
741
754
  const headRepoOwner =
742
755
  gh.headRepository?.owner?.login ??
743
756
  gh.headRepositoryOwner?.login ??
@@ -745,28 +758,36 @@ export function decideAuthorshipFromGh(gh, viewerLogin, baseRepoOwner = null) {
745
758
  null
746
759
  // Prefer baseRepoOwner when supplied; fall back to viewerLogin only for
747
760
  // older callers that haven't been updated yet (e.g. legacy tests). The
748
- // fallback preserves prior behavior for the personal-repo case.
761
+ // fallback preserves prior fork-detection behavior for the personal-repo
762
+ // case.
749
763
  const compareOwner = baseRepoOwner ?? viewerLogin
750
764
  const isFork = headRepoOwner && compareOwner && headRepoOwner !== compareOwner
751
765
 
752
- const ownerLike = ['OWNER', 'MEMBER', 'COLLABORATOR']
753
766
  const externalLike = ['NONE', 'CONTRIBUTOR', 'FIRST_TIME_CONTRIBUTOR', 'FIRST_TIMER']
754
767
 
755
- if (!isFork && association && ownerLike.includes(association)) {
768
+ // G44: owned ⟺ (not a fork) AND the PR was authored by the running viewer.
769
+ if (!isFork && author && viewerLogin && author === viewerLogin) {
756
770
  return {
757
771
  authorship_mode: 'owned',
758
- authorship_signal: `authorAssociation=${association}, same-repo branch (head=${headRepoOwner ?? '?'}, base=${compareOwner ?? '?'})`,
772
+ authorship_signal: `authored-by-viewer (author=${author}, viewer=${viewerLogin}), same-repo branch (head=${headRepoOwner ?? '?'}, base=${compareOwner ?? '?'}), authorAssociation=${association ?? 'null'}`,
759
773
  }
760
774
  }
761
- if (isFork || (association && externalLike.includes(association))) {
775
+ // Forks, external-like associations, AND non-fork PRs authored by someone
776
+ // other than the viewer (a teammate's same-repo PR — the G44 case) all
777
+ // resolve to external (comment-only).
778
+ if (
779
+ isFork ||
780
+ (association && externalLike.includes(association)) ||
781
+ (!isFork && author && viewerLogin && author !== viewerLogin)
782
+ ) {
762
783
  return {
763
784
  authorship_mode: 'external',
764
- authorship_signal: `authorAssociation=${association}, fork=${Boolean(isFork)} (head=${headRepoOwner ?? '?'}, base=${compareOwner ?? '?'})`,
785
+ authorship_signal: `authorAssociation=${association ?? 'null'}, fork=${Boolean(isFork)}, author=${author ?? '?'}, viewer=${viewerLogin ?? '?'} (head=${headRepoOwner ?? '?'}, base=${compareOwner ?? '?'})`,
765
786
  }
766
787
  }
767
788
  return {
768
789
  authorship_mode: 'unknown',
769
- authorship_signal: `authorAssociation=${association ?? 'null'}, fork=${Boolean(isFork)}`,
790
+ authorship_signal: `authorAssociation=${association ?? 'null'}, fork=${Boolean(isFork)}, author=${author ?? 'null'}`,
770
791
  }
771
792
  }
772
793