session-orchestrator 3.22.0 → 3.23.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/commands/autopilot-multi.md +14 -0
- package/.cursor/commands/autopilot.md +14 -0
- package/.cursor/commands/bootstrap.md +14 -0
- package/.cursor/commands/brainstorm.md +14 -0
- package/.cursor/commands/close.md +13 -0
- package/.cursor/commands/contract-version-bump.md +14 -0
- package/.cursor/commands/debug.md +14 -0
- package/.cursor/commands/discovery.md +14 -0
- package/.cursor/commands/dispatcher.md +14 -0
- package/.cursor/commands/eli5.md +14 -0
- package/.cursor/commands/eval.md +14 -0
- package/.cursor/commands/evolve.md +14 -0
- package/.cursor/commands/go.md +14 -0
- package/.cursor/commands/grill.md +14 -0
- package/.cursor/commands/harness-audit.md +13 -0
- package/.cursor/commands/journey-audit.md +14 -0
- package/.cursor/commands/memory-cleanup.md +14 -0
- package/.cursor/commands/persona-panel.md +14 -0
- package/.cursor/commands/plan.md +14 -0
- package/.cursor/commands/portfolio.md +14 -0
- package/.cursor/commands/reconcile.md +14 -0
- package/.cursor/commands/release.md +14 -0
- package/.cursor/commands/repo-audit.md +13 -0
- package/.cursor/commands/session.md +14 -0
- package/.cursor/commands/spinout.md +14 -0
- package/.cursor/commands/sunset-review.md +14 -0
- package/.cursor/commands/templates-ack.md +14 -0
- package/.cursor/commands/test.md +14 -0
- package/.cursor/hooks.json +60 -0
- package/.cursor/rules/000-session-orchestrator.mdc +8 -0
- package/.cursor/rules/010-session-workflow.mdc +9 -1
- package/.cursor/rules/020-quality-gates.mdc +1 -1
- package/.cursor/rules/030-wave-execution.mdc +1 -1
- package/.cursor/rules/050-plan.mdc +2 -2
- package/.cursor/rules/070-gitlab-ops.mdc +73 -57
- package/.cursor/rules/080-ecosystem-health.mdc +7 -7
- package/.cursor/skills/architecture/SKILL.md +13 -0
- package/.cursor/skills/autopilot/SKILL.md +12 -0
- package/.cursor/skills/bootstrap/SKILL.md +12 -0
- package/.cursor/skills/brainstorm/SKILL.md +13 -0
- package/.cursor/skills/claude-md-drift-check/SKILL.md +13 -0
- package/.cursor/skills/contract-version-bump/SKILL.md +12 -0
- package/.cursor/skills/convergence-monitoring/SKILL.md +12 -0
- package/.cursor/skills/daily/SKILL.md +12 -0
- package/.cursor/skills/debug/SKILL.md +13 -0
- package/.cursor/skills/discovery/SKILL.md +13 -0
- package/.cursor/skills/dispatcher/SKILL.md +13 -0
- package/.cursor/skills/docs-orchestrator/SKILL.md +13 -0
- package/.cursor/skills/domain-model/SKILL.md +13 -0
- package/.cursor/skills/ecosystem-health/SKILL.md +13 -0
- package/.cursor/skills/eli5/SKILL.md +13 -0
- package/.cursor/skills/eval/SKILL.md +12 -0
- package/.cursor/skills/evolve/SKILL.md +13 -0
- package/.cursor/skills/frontmatter-guard/SKILL.md +13 -0
- package/.cursor/skills/gitlab-ops/SKILL.md +13 -0
- package/.cursor/skills/gitlab-portfolio/SKILL.md +13 -0
- package/.cursor/skills/grill/SKILL.md +13 -0
- package/.cursor/skills/hook-development/SKILL.md +13 -0
- package/.cursor/skills/journey-audit/SKILL.md +13 -0
- package/.cursor/skills/mcp-builder/SKILL.md +13 -0
- package/.cursor/skills/memory-cleanup/SKILL.md +12 -0
- package/.cursor/skills/mode-selector/SKILL.md +13 -0
- package/.cursor/skills/npm-publish/SKILL.md +12 -0
- package/.cursor/skills/peekaboo-driver/SKILL.md +13 -0
- package/.cursor/skills/persona-panel/SKILL.md +12 -0
- package/.cursor/skills/plan/SKILL.md +13 -0
- package/.cursor/skills/playwright-driver/SKILL.md +13 -0
- package/.cursor/skills/quality-gates/SKILL.md +13 -0
- package/.cursor/skills/reconcile/SKILL.md +12 -0
- package/.cursor/skills/repo-audit/SKILL.md +13 -0
- package/.cursor/skills/session-end/SKILL.md +13 -0
- package/.cursor/skills/session-plan/SKILL.md +13 -0
- package/.cursor/skills/session-start/SKILL.md +13 -0
- package/.cursor/skills/skill-creator/SKILL.md +13 -0
- package/.cursor/skills/spinout/SKILL.md +12 -0
- package/.cursor/skills/sunset-review/SKILL.md +13 -0
- package/.cursor/skills/test-runner/SKILL.md +13 -0
- package/.cursor/skills/tmux-layout/SKILL.md +13 -0
- package/.cursor/skills/ubiquitous-language/SKILL.md +13 -0
- package/.cursor/skills/using-orchestrator/SKILL.md +13 -0
- package/.cursor/skills/vault-mirror/SKILL.md +13 -0
- package/.cursor/skills/vault-sync/SKILL.md +13 -0
- package/.cursor/skills/wave-executor/SKILL.md +13 -0
- package/.cursor/skills/write-executable-plan/SKILL.md +13 -0
- package/.mcp.json +4 -1
- package/CHANGELOG.md +168 -0
- package/README.md +18 -15
- package/agents/AGENTS.md +23 -4
- package/agents/code-implementer.md +2 -1
- package/agents/db-specialist.md +2 -1
- package/agents/docs-writer.md +3 -1
- package/agents/eval-judge.md +1 -1
- package/agents/session-reviewer.md +7 -1
- package/agents/test-writer.md +2 -1
- package/agents/ui-developer.md +2 -1
- package/commands/bootstrap.md +2 -2
- package/commands/close.md +3 -1
- package/commands/go.md +1 -1
- package/commands/journey-audit.md +43 -0
- package/docs/USER-GUIDE.md +2 -2
- package/docs/ci-setup.md +14 -0
- package/docs/codex-setup.md +64 -0
- package/docs/components.md +6 -6
- package/docs/cursor-setup.md +26 -47
- package/docs/events-schema.md +76 -4
- package/docs/github-mirror-protection.md +197 -0
- package/docs/pi-setup.md +2 -0
- package/docs/rule-authoring.md +3 -1
- package/docs/scope-collision-guard.md +49 -2
- package/docs/session-config-reference.md +26 -4
- package/docs/session-config-template.md +4 -3
- package/docs/telemetry.md +22 -0
- package/hooks/_lib/lock-bootstrap.mjs +8 -4
- package/hooks/_lib/vcs-create-matcher.mjs +397 -38
- package/hooks/enforce-scope.mjs +64 -0
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +201 -20
- package/hooks/hooks-pi.json +1 -1
- package/hooks/hooks.json +2 -2
- package/hooks/on-session-end.mjs +211 -10
- package/hooks/on-session-start.mjs +214 -11
- package/hooks/on-stop.mjs +48 -9
- package/hooks/post-subagent-discovery-validator.mjs +34 -3
- package/hooks/post-tool-batch-wave-signal.mjs +11 -2
- package/hooks/pre-bash-issue-budget.mjs +117 -4
- package/hooks/pre-bash-sessions-ledger-guard.mjs +159 -0
- package/hooks/pre-bash-staging-fence.mjs +4 -0
- package/hooks/pre-task-scope-disjoint.mjs +368 -35
- package/hooks/skill-invocation-telemetry.mjs +21 -10
- package/monitors/monitors.json +6 -0
- package/package.json +1 -1
- package/pi/prompts/journey-audit.md +12 -0
- package/rules/_index.md +9 -1
- package/rules/always-on/ask-via-tool.md +62 -0
- package/rules/always-on/bash-harness-pitfalls.md +168 -0
- package/rules/always-on/build-value.md +47 -0
- package/rules/always-on/cross-session-messaging.md +59 -0
- package/rules/always-on/loop-and-monitor.md +221 -0
- package/rules/always-on/parallel-sessions.md +142 -12
- package/rules/always-on/receiving-review.md +108 -0
- package/rules/always-on/test-value.md +40 -0
- package/rules/always-on/verification-before-completion.md +77 -0
- package/scripts/archive-closed-prds.mjs +258 -18
- package/scripts/autopilot.mjs +5 -0
- package/scripts/backfill-evidence-digest.mjs +376 -0
- package/scripts/cursor-install.mjs +89 -48
- package/scripts/export-hw-learnings.mjs +143 -2
- package/scripts/express-path.mjs +299 -0
- package/scripts/generate-cursor-adapter.mjs +253 -0
- package/scripts/github-protection-audit.mjs +358 -0
- package/scripts/lib/autopilot/worktree-pipeline.mjs +240 -16
- package/scripts/lib/build-live-signals.mjs +24 -5
- package/scripts/lib/ci-status-banner.mjs +158 -11
- package/scripts/lib/command-blocker.mjs +70 -0
- package/scripts/lib/config/reconcile.mjs +79 -4
- package/scripts/lib/config/section-extractor.mjs +235 -36
- package/scripts/lib/config-schema.mjs +9 -1
- package/scripts/lib/config.mjs +57 -6
- package/scripts/lib/convergence-monitor.mjs +13 -2
- package/scripts/lib/cursor-hook-bridge.mjs +443 -0
- package/scripts/lib/dispatcher/cli.mjs +2 -2
- package/scripts/lib/express-path.mjs +327 -0
- package/scripts/lib/file-lock.mjs +22 -4
- package/scripts/lib/gates/gate-full.mjs +81 -8
- package/scripts/lib/gates/gate-helpers.mjs +76 -15
- package/scripts/lib/git-config-drift.mjs +134 -5
- package/scripts/lib/host-identity.mjs +247 -2
- package/scripts/lib/instruction-budget-guard.mjs +31 -1
- package/scripts/lib/issue-budget.mjs +229 -30
- package/scripts/lib/learnings/io.mjs +55 -10
- package/scripts/lib/learnings/schema.mjs +95 -28
- package/scripts/lib/lock-reaper.mjs +7 -1
- package/scripts/lib/locks/staging-fence-lock.mjs +5 -1
- package/scripts/lib/locks/state-md-lock.mjs +8 -1
- package/scripts/lib/memory-banner.mjs +5 -2
- package/scripts/lib/memory-paths.mjs +15 -6
- package/scripts/lib/mode-selector/scoring.mjs +53 -6
- package/scripts/lib/platform.mjs +72 -9
- package/scripts/lib/plugin-root.mjs +143 -19
- package/scripts/lib/project-hygiene.mjs +43 -3
- package/scripts/lib/quality-gate.mjs +271 -13
- package/scripts/lib/reconcile/emitter.mjs +87 -19
- package/scripts/lib/reconcile/engine.mjs +281 -13
- package/scripts/lib/reconcile/idempotency.mjs +102 -1
- package/scripts/lib/reconcile/renderer.mjs +148 -3
- package/scripts/lib/reconcile/sanitize.mjs +40 -17
- package/scripts/lib/reconcile/writer.mjs +415 -84
- package/scripts/lib/rule-loader.mjs +37 -2
- package/scripts/lib/rules-sync.mjs +51 -8
- package/scripts/lib/scope-gate.mjs +90 -0
- package/scripts/lib/session-close-backfill.mjs +369 -28
- package/scripts/lib/session-discovery.mjs +13 -3
- package/scripts/lib/session-end/phase-skip.mjs +37 -4
- package/scripts/lib/session-end/worktree-cleanup.mjs +154 -7
- package/scripts/lib/session-id.mjs +30 -14
- package/scripts/lib/session-identity/own-session.mjs +159 -0
- package/scripts/lib/session-lock.mjs +85 -30
- package/scripts/lib/session-schema/normalizer.mjs +70 -3
- package/scripts/lib/session-schema/validator.mjs +40 -0
- package/scripts/lib/session-start-probes.mjs +608 -0
- package/scripts/lib/session-transition.mjs +277 -0
- package/scripts/lib/sessions-staleness-banner.mjs +124 -57
- package/scripts/lib/spiral-carryover.mjs +90 -9
- package/scripts/lib/state-md/frontmatter-mutators.mjs +41 -8
- package/scripts/lib/state-md/mission-status.mjs +350 -52
- package/scripts/lib/state-md/yaml-parser.mjs +145 -16
- package/scripts/lib/state-md.mjs +12 -2
- package/scripts/lib/telemetry/sync.mjs +46 -8
- package/scripts/lib/validate/check-agents.mjs +66 -0
- package/scripts/lib/validate/check-cursor-adapter.mjs +102 -0
- package/scripts/lib/validate/check-dead-bridge.mjs +24 -2
- package/scripts/lib/validate/check-doc-cli-commands.mjs +16 -32
- package/scripts/lib/validate/check-hooks-symmetry.mjs +29 -63
- package/scripts/lib/validate/check-playwright-mcp-canary.mjs +13 -22
- package/scripts/lib/validate/check-plugin-monitors.mjs +10 -4
- package/scripts/lib/validate/check-test-value-bans.mjs +165 -17
- package/scripts/lib/validate/check-unwired-features.mjs +340 -32
- package/scripts/lib/validate/repo-files.mjs +275 -0
- package/scripts/lib/validate-vendored-rules.mjs +229 -7
- package/scripts/lib/vault-mirror/process.mjs +99 -43
- package/scripts/lib/vault-mirror/telemetry.mjs +210 -0
- package/scripts/lib/vault-staleness-banner.mjs +76 -6
- package/scripts/lib/vault-status/board-writer.mjs +211 -10
- package/scripts/lib/vault-status/narrative-mirror.mjs +188 -8
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +832 -0
- package/scripts/lib/wave-transcript-tail.mjs +869 -0
- package/scripts/materialize-wave-scope.mjs +209 -12
- package/scripts/mcp-server.sh +11 -2
- package/scripts/parse-config.mjs +65 -0
- package/scripts/token-audit.sh +9 -2
- package/scripts/validate-plugin.mjs +3 -0
- package/scripts/validate-wave-scope.mjs +67 -0
- package/scripts/vault-mirror.mjs +203 -34
- package/skills/_shared/monitor-patterns.md +31 -5
- package/skills/_shared/parallel-aware-auq.md +1 -1
- package/skills/_shared/parallel-aware-preamble.md +4 -2
- package/skills/_shared/platform-tools.md +11 -5
- package/skills/_shared/state-ownership.md +29 -2
- package/skills/autopilot/SKILL.md +5 -1
- package/skills/bootstrap/SKILL.md +3 -3
- package/skills/bootstrap/_shared-template.md +18 -10
- package/skills/bootstrap/deep-template.md +10 -6
- package/skills/bootstrap/fast-template.md +15 -8
- package/skills/bootstrap/standard-template.md +10 -6
- package/skills/claude-md-drift-check/checker.mjs +39 -11
- package/skills/dispatcher/SKILL.md +1 -1
- package/skills/journey-audit/SKILL.md +269 -0
- package/skills/peekaboo-driver/SKILL.md +15 -3
- package/skills/persona-panel/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +41 -1
- package/skills/session-end/SKILL.md +17 -4
- package/skills/session-end/metrics-collection.md +7 -4
- package/skills/session-end/phase-3-6-tail.md +11 -3
- package/skills/session-end/phase-3-7a-recommendations.md +16 -2
- package/skills/session-plan/SKILL.md +6 -1
- package/skills/session-plan/wave-template.md +1 -0
- package/skills/session-start/SKILL.md +30 -16
- package/skills/session-start/phase-7-5-mode-selector.md +15 -3
- package/skills/session-start/phase-8-5-express-path.md +77 -12
- package/skills/vault-sync/validator.mjs +31 -0
- package/skills/wave-executor/SKILL.md +4 -2
- package/skills/wave-executor/circuit-breaker.md +34 -9
- package/skills/wave-executor/wave-loop.md +102 -19
- package/templates/_shared/journey-manifest.md +110 -0
- package/templates/_shared/rules/parallel-sessions.md +0 -77
package/hooks/hooks-cursor.json
CHANGED
|
@@ -1,24 +1,205 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_comment": "Cursor IDE hook mapping
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"_enforcement_note": "MACHINE-READABLE STATUS (#919): every entry below is an intended mapping, NOT live enforcement. No Cursor payload adapter exists (Pi has scripts/lib/pi-hook-bridge.mjs; Cursor has nothing), so each handler short-circuits on a Cursor-shaped payload and writes 0 bytes with exit 0. Operator decision 2026-07-31: gap registered, not closed. The machine-readable counterpart of this key is DOCUMENTED_ASYMMETRIES.handlerAsymmetries.cursor in scripts/lib/validate/check-hooks-symmetry.mjs, which Check 6 counts per event; the projection that makes that comparison non-vacuous is DOCUMENTED_ASYMMETRIES.cursorEventMap.",
|
|
7
|
-
"_issues": ["#919", "#946"],
|
|
2
|
+
"_comment": "Cursor IDE hook mapping. Event names are Cursor-native; commands receive Claude/Codex-compatible hook JSON on stdin after scripts/lib/cursor-hook-bridge.mjs normalises the payload. CURSOR_PLUGIN_ROOT is set by the bridge; CURSOR_RULES_DIR is the compatibility alias used by plugin-root resolution.",
|
|
3
|
+
"_enforcement": "live",
|
|
4
|
+
"_enforcement_note": "LIVE via scripts/lib/cursor-hook-bridge.mjs and .cursor/hooks.json. The bridge maps Cursor stdin onto tool_name / tool_input and maps emitDeny envelopes onto Cursor { permission }. afterFileEdit remains post-hoc (cannot prevent an edit already written).",
|
|
5
|
+
"_issues": ["#919"],
|
|
8
6
|
"hooks": {
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
7
|
+
"sessionStart": [
|
|
8
|
+
{
|
|
9
|
+
"matcher": "",
|
|
10
|
+
"hooks": [
|
|
11
|
+
{
|
|
12
|
+
"type": "command",
|
|
13
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/on-session-start.mjs\"",
|
|
14
|
+
"timeout": 5
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"sessionEnd": [
|
|
20
|
+
{
|
|
21
|
+
"matcher": "",
|
|
22
|
+
"hooks": [
|
|
23
|
+
{
|
|
24
|
+
"type": "command",
|
|
25
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/on-session-end.mjs\"",
|
|
26
|
+
"timeout": 5
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"beforeShellExecution": [
|
|
32
|
+
{
|
|
33
|
+
"matcher": "*",
|
|
34
|
+
"hooks": [
|
|
35
|
+
{
|
|
36
|
+
"type": "command",
|
|
37
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/pre-bash-destructive-guard.mjs\"",
|
|
38
|
+
"timeout": 5
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"type": "command",
|
|
42
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/pre-bash-staging-fence.mjs\"",
|
|
43
|
+
"timeout": 5
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"type": "command",
|
|
47
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/pre-bash-memory-propose-audit.mjs\"",
|
|
48
|
+
"timeout": 5
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"type": "command",
|
|
52
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/pre-bash-sessions-ledger-guard.mjs\"",
|
|
53
|
+
"timeout": 5
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"type": "command",
|
|
57
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/pre-bash-templates-first.mjs\"",
|
|
58
|
+
"timeout": 5
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"type": "command",
|
|
62
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/pre-bash-issue-budget.mjs\"",
|
|
63
|
+
"timeout": 5
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"type": "command",
|
|
67
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/enforce-commands.mjs\"",
|
|
68
|
+
"timeout": 5
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
"preToolUse": [
|
|
74
|
+
{
|
|
75
|
+
"matcher": "Write|StrReplace|TabWrite|Edit|MultiEdit",
|
|
76
|
+
"hooks": [
|
|
77
|
+
{
|
|
78
|
+
"type": "command",
|
|
79
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/enforce-scope.mjs\"",
|
|
80
|
+
"timeout": 5
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"type": "command",
|
|
84
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/config-protection.mjs\"",
|
|
85
|
+
"timeout": 5
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
"afterShellExecution": [
|
|
91
|
+
{
|
|
92
|
+
"matcher": "*",
|
|
93
|
+
"hooks": [
|
|
94
|
+
{
|
|
95
|
+
"type": "command",
|
|
96
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/post-bash-write-verify.mjs\"",
|
|
97
|
+
"timeout": 5
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
],
|
|
102
|
+
"postToolUse": [
|
|
103
|
+
{
|
|
104
|
+
"matcher": "Write|StrReplace|TabWrite|Edit|MultiEdit",
|
|
105
|
+
"hooks": [
|
|
106
|
+
{
|
|
107
|
+
"type": "command",
|
|
108
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/post-edit-validate.mjs\"",
|
|
109
|
+
"timeout": 5
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"type": "command",
|
|
113
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/post-tooluse-frontend-slop.mjs\"",
|
|
114
|
+
"timeout": 5
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"matcher": "*",
|
|
120
|
+
"hooks": [
|
|
121
|
+
{
|
|
122
|
+
"type": "command",
|
|
123
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/loop-guard.mjs\"",
|
|
124
|
+
"timeout": 5
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
"afterFileEdit": [
|
|
130
|
+
{
|
|
131
|
+
"matcher": "*",
|
|
132
|
+
"hooks": [
|
|
133
|
+
{
|
|
134
|
+
"type": "command",
|
|
135
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/post-edit-validate.mjs\"",
|
|
136
|
+
"timeout": 5
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"type": "command",
|
|
140
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/post-tooluse-frontend-slop.mjs\"",
|
|
141
|
+
"timeout": 5
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
],
|
|
146
|
+
"postToolUseFailure": [
|
|
147
|
+
{
|
|
148
|
+
"matcher": "",
|
|
149
|
+
"hooks": [
|
|
150
|
+
{
|
|
151
|
+
"type": "command",
|
|
152
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/post-tool-failure-corrective-context.mjs\"",
|
|
153
|
+
"timeout": 5
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
],
|
|
158
|
+
"stop": [
|
|
159
|
+
{
|
|
160
|
+
"matcher": "",
|
|
161
|
+
"hooks": [
|
|
162
|
+
{
|
|
163
|
+
"type": "command",
|
|
164
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/on-stop.mjs\"",
|
|
165
|
+
"timeout": 5
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
],
|
|
170
|
+
"subagentStart": [
|
|
171
|
+
{
|
|
172
|
+
"matcher": "",
|
|
173
|
+
"hooks": [
|
|
174
|
+
{
|
|
175
|
+
"type": "command",
|
|
176
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/subagent-telemetry.mjs\"",
|
|
177
|
+
"timeout": 5
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
],
|
|
182
|
+
"subagentStop": [
|
|
183
|
+
{
|
|
184
|
+
"matcher": "",
|
|
185
|
+
"hooks": [
|
|
186
|
+
{
|
|
187
|
+
"type": "command",
|
|
188
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/on-stop.mjs\"",
|
|
189
|
+
"timeout": 5
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"type": "command",
|
|
193
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/subagent-telemetry.mjs\"",
|
|
194
|
+
"timeout": 5
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"type": "command",
|
|
198
|
+
"command": "sh \"$CURSOR_PLUGIN_ROOT/hooks/run-node.sh\" \"$CURSOR_PLUGIN_ROOT/hooks/post-subagent-discovery-validator.mjs\"",
|
|
199
|
+
"timeout": 5
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
]
|
|
23
204
|
}
|
|
24
205
|
}
|
package/hooks/hooks-pi.json
CHANGED
package/hooks/hooks.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
9
|
-
"command": "echo '🎯 Session Orchestrator v3.
|
|
9
|
+
"command": "echo '🎯 Session Orchestrator v3.23.0 — /session [housekeeping|feature|deep] | /plan [new|feature|retro] | /discovery [scope] | /evolve [analyze|review|list]'",
|
|
10
10
|
"async": false
|
|
11
11
|
},
|
|
12
12
|
{
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
{
|
|
26
26
|
"type": "command",
|
|
27
27
|
"command": "sh \"$CLAUDE_PLUGIN_ROOT/hooks/run-node.sh\" \"$CLAUDE_PLUGIN_ROOT/hooks/on-session-end.mjs\"",
|
|
28
|
-
"timeout":
|
|
28
|
+
"timeout": 10
|
|
29
29
|
}
|
|
30
30
|
]
|
|
31
31
|
}
|
package/hooks/on-session-end.mjs
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* `session.started` had no terminal partner.
|
|
11
11
|
*
|
|
12
12
|
* JSONL format (`.orchestrator/metrics/events.jsonl`):
|
|
13
|
-
* {"timestamp":<ISO>,"event":"orchestrator.session.ended","session_id":"...","reason":"<reason>","duration_ms":<int>}
|
|
13
|
+
* {"timestamp":<ISO>,"event":"orchestrator.session.ended","session_id":"...","semantic_session_id":"...","reason":"<reason>","duration_ms":<int>}
|
|
14
|
+
* (`session_id` / `semantic_session_id` are omitted when unresolvable — #1068 AC1.)
|
|
14
15
|
*
|
|
15
16
|
* Exit codes: 0 always (informational hook — must never block session teardown).
|
|
16
17
|
* stdin: optional JSON { hook_event_name:"SessionEnd", session_id?, reason?, cwd? }.
|
|
@@ -30,14 +31,20 @@ if (!shouldRunHook('on-session-end')) process.exit(0);
|
|
|
30
31
|
|
|
31
32
|
import { emitEvent } from '../scripts/lib/events.mjs';
|
|
32
33
|
import { SO_PROJECT_DIR } from '../scripts/lib/platform.mjs';
|
|
33
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
backfillAbandonedSession,
|
|
36
|
+
backfillCompletedFromStateMd,
|
|
37
|
+
} from '../scripts/lib/session-close-backfill.mjs';
|
|
34
38
|
import {
|
|
35
39
|
readLockDetailed,
|
|
36
40
|
release,
|
|
37
41
|
loadOwnerProof,
|
|
38
42
|
OWNER_PROOF_RELPATH,
|
|
39
43
|
} from '../scripts/lib/session-lock.mjs';
|
|
44
|
+
import { parseSessionId } from '../scripts/lib/session-id.mjs';
|
|
40
45
|
import { deregisterSelf, logSweepEvent } from '../scripts/lib/session-registry.mjs';
|
|
46
|
+
import { readConfigFile, parseSessionConfig } from '../scripts/lib/config.mjs';
|
|
47
|
+
import { flush } from '../scripts/lib/telemetry/sync.mjs';
|
|
41
48
|
import { attemptLockReconciliation } from './_lib/lock-reconcile.mjs';
|
|
42
49
|
|
|
43
50
|
// ---------------------------------------------------------------------------
|
|
@@ -72,11 +79,26 @@ async function readStdinJson() {
|
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
/**
|
|
75
|
-
* Resolve this session's id + duration + semantic id.
|
|
76
|
-
* otherwise fall back to
|
|
77
|
-
* on-session-start.mjs).
|
|
78
|
-
* is
|
|
79
|
-
* mismatched / unknown session.
|
|
82
|
+
* Resolve this session's id + duration + semantic id. A stdin session_id wins
|
|
83
|
+
* ONLY when it parses as a UUID; otherwise fall back to
|
|
84
|
+
* `.orchestrator/current-session.json` (written by on-session-start.mjs).
|
|
85
|
+
* duration_ms is only computed when the ENDING session is the one recorded in
|
|
86
|
+
* current-session.json — never fabricated for a mismatched / unknown session.
|
|
87
|
+
*
|
|
88
|
+
* #1091 / Kanevry#66 — WRITER/READER SYMMETRY. `on-session-start.mjs`
|
|
89
|
+
* (`resolveSessionId`, :316-317) accepts a stdin raw id only when
|
|
90
|
+
* `parseSessionId(fromStdin)?.format === 'uuid'` and otherwise mints a
|
|
91
|
+
* `randomUUID()`; `current-session.json`, `session.lock` and the host registry
|
|
92
|
+
* are therefore ALWAYS keyed by a UUID. This reader used to accept ANY
|
|
93
|
+
* non-empty stdin string, so a harness that passed a non-UUID id
|
|
94
|
+
* (`{"session_id":"not-a-uuid"}`) resolved a key that matches nothing written
|
|
95
|
+
* at start: the raw-ID ownership compare below (:330) fails, the lock is
|
|
96
|
+
* neither released nor reconciled, and it LEAKS until its TTL expires. Same
|
|
97
|
+
* for `deregisterSelf()`, whose registry file is named after the id
|
|
98
|
+
* `registerSelf()` used. Mirroring the writer's rule here makes the fallback
|
|
99
|
+
* (which reads exactly those artifacts) the single source of the identity.
|
|
100
|
+
* The ownership compare itself stays an exact string `===` — this changes
|
|
101
|
+
* WHICH id is compared, never HOW.
|
|
80
102
|
*
|
|
81
103
|
* `semanticSessionId` is read from current-session.json (present since #587):
|
|
82
104
|
* it is the SEMANTIC id (`<branch>-<date>-<mode>-<n>`) that sessions.jsonl is
|
|
@@ -98,7 +120,8 @@ async function readStdinJson() {
|
|
|
98
120
|
*/
|
|
99
121
|
async function resolveSession(input, projectRoot) {
|
|
100
122
|
const fromStdin = input?.session_id ?? input?.sessionId ?? null;
|
|
101
|
-
|
|
123
|
+
// UUID-only, exactly as the writer decides it (see the docblock above).
|
|
124
|
+
let sessionId = parseSessionId(fromStdin)?.format === 'uuid' ? fromStdin : null;
|
|
102
125
|
|
|
103
126
|
let recordedId = null;
|
|
104
127
|
let semanticSessionId = null;
|
|
@@ -138,6 +161,152 @@ async function resolveSession(input, projectRoot) {
|
|
|
138
161
|
return { sessionId, semanticSessionId: resolvedSemanticSessionId, durationMs };
|
|
139
162
|
}
|
|
140
163
|
|
|
164
|
+
/**
|
|
165
|
+
* POST budget for the close-time telemetry flush (ms).
|
|
166
|
+
*
|
|
167
|
+
* NAMED CEILING, and the reason it is BELOW the module default POST_TIMEOUT_MS
|
|
168
|
+
* (3000): the two timeouts fail differently.
|
|
169
|
+
*
|
|
170
|
+
* - This one expiring is LOSSLESS. `flush()` catches the abort and routes the
|
|
171
|
+
* record into the bounded offline queue; the daily fallback
|
|
172
|
+
* (`shouldDailyFlush`) drains it on a later session.
|
|
173
|
+
* - The HARNESS timeout expiring is LOSSY. Claude Code kills the hook process
|
|
174
|
+
* mid-flight, so the enqueue never runs and the record is simply gone —
|
|
175
|
+
* which is the exact failure #1138 exists to remove.
|
|
176
|
+
*
|
|
177
|
+
* So the internal bound must stay comfortably under the harness bound, not
|
|
178
|
+
* merely below it. Budget (hooks/hooks.json `SessionEnd.timeout: 10` s, raised
|
|
179
|
+
* from 5 with this change): up to 500 ms stdin + a backfill measured at a
|
|
180
|
+
* ~845 ms median (its TAIL, not its median, is what would collide) + lock
|
|
181
|
+
* release + deregistration, then this 2 s. That leaves several seconds of slack
|
|
182
|
+
* for the backfill tail while still covering a normal round-trip many times
|
|
183
|
+
* over.
|
|
184
|
+
*
|
|
185
|
+
* Revisit if the hooks.json SessionEnd timeout changes — the pin lives in
|
|
186
|
+
* tests/hooks/on-session-end.test.mjs.
|
|
187
|
+
*/
|
|
188
|
+
const TELEMETRY_FLUSH_TIMEOUT_MS = 2000;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Read `persistence` from the repo's Session Config. Defaults to `true` — the
|
|
192
|
+
* same default `scripts/lib/config.mjs` applies — so an unreadable or absent
|
|
193
|
+
* CLAUDE.md (or its Codex alias AGENTS.md) never silently disables the flush.
|
|
194
|
+
*
|
|
195
|
+
* @param {string} projectRoot
|
|
196
|
+
* @returns {Promise<boolean>}
|
|
197
|
+
*/
|
|
198
|
+
async function readPersistence(projectRoot) {
|
|
199
|
+
try {
|
|
200
|
+
const md = await readConfigFile(projectRoot);
|
|
201
|
+
return parseSessionConfig(md).persistence !== false;
|
|
202
|
+
} catch {
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Reduce a `flush()` result to the two-field breadcrumb the event carries.
|
|
209
|
+
*
|
|
210
|
+
* `reason` is normalised to its head token because `flush()` may return
|
|
211
|
+
* `build-error: <message>`, and a raw error message is unbounded free text in a
|
|
212
|
+
* stream whose whole purpose is aggregation by class.
|
|
213
|
+
*
|
|
214
|
+
* @param {{sent?: boolean, queued?: boolean, reason?: string}|null|undefined} res
|
|
215
|
+
* @returns {{outcome: 'sent'|'queued'|'gated'|'skipped', reason: string}}
|
|
216
|
+
*/
|
|
217
|
+
function classifyFlush(res) {
|
|
218
|
+
const reason = String(res?.reason ?? 'unknown').split(':')[0];
|
|
219
|
+
if (res?.sent === true) return { outcome: 'sent', reason };
|
|
220
|
+
if (res?.queued === true) return { outcome: 'queued', reason };
|
|
221
|
+
if (reason === 'gated') return { outcome: 'gated', reason };
|
|
222
|
+
return { outcome: 'skipped', reason };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* #1138 — the MECHANICAL telemetry flush.
|
|
227
|
+
*
|
|
228
|
+
* Until now the close-time flush existed only as prose in
|
|
229
|
+
* `skills/session-end/SKILL.md` § Phase 3.45, i.e. it ran only when the
|
|
230
|
+
* coordinator LLM happened to execute that phase. Measured 2026-08-23: 588
|
|
231
|
+
* session closes across 13 repos produced 82 ingest records (~14%). A hook is
|
|
232
|
+
* the only caller that fires on EVERY close, including the ones that never
|
|
233
|
+
* reach `/close` at all.
|
|
234
|
+
*
|
|
235
|
+
* Strictly best-effort: never throws, and bounded by TELEMETRY_FLUSH_TIMEOUT_MS.
|
|
236
|
+
* The consent gate lives INSIDE `flush()` (`resolveConsent()` is its first
|
|
237
|
+
* statement) — this function deliberately does not re-implement it, so there is
|
|
238
|
+
* exactly one place where "may we send?" is decided.
|
|
239
|
+
*
|
|
240
|
+
* Always emits `orchestrator.telemetry.flush` with `{ outcome, reason }` and
|
|
241
|
+
* NOTHING else — no payload, no anon_id. Per `.claude/rules/host-resources.md`
|
|
242
|
+
* HR-105, a mechanism whose firing rate nothing records cannot be falsified;
|
|
243
|
+
* this event is what makes the flush rate measurable next time.
|
|
244
|
+
*
|
|
245
|
+
* @param {string} projectRoot
|
|
246
|
+
* @returns {Promise<void>}
|
|
247
|
+
*/
|
|
248
|
+
async function flushTelemetry(projectRoot) {
|
|
249
|
+
let result;
|
|
250
|
+
try {
|
|
251
|
+
result = (await readPersistence(projectRoot))
|
|
252
|
+
? classifyFlush(await flush({
|
|
253
|
+
metricsDir: path.join(projectRoot, '.orchestrator', 'metrics'),
|
|
254
|
+
timeoutMs: TELEMETRY_FLUSH_TIMEOUT_MS,
|
|
255
|
+
}))
|
|
256
|
+
// `persistence: false` means this session leaves no durable local trace;
|
|
257
|
+
// a telemetry ping is a durable record too, so it honours the same switch.
|
|
258
|
+
: { outcome: 'skipped', reason: 'persistence-disabled' };
|
|
259
|
+
} catch {
|
|
260
|
+
result = { outcome: 'skipped', reason: 'error' };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// `result` is passed through verbatim: it holds EXACTLY {outcome, reason}, so
|
|
264
|
+
// no payload field can leak into the event by accident.
|
|
265
|
+
try {
|
|
266
|
+
await emitEvent('orchestrator.telemetry.flush', result);
|
|
267
|
+
} catch { /* observability is best-effort */ }
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* #1068 AC2 — emit ONE canonically queryable outcome per backfill attempt.
|
|
272
|
+
*
|
|
273
|
+
* Until now a backfill result reached exactly one place:
|
|
274
|
+
* `.orchestrator/metrics/session-close-backfill.log`, a side-log no lifecycle
|
|
275
|
+
* consumer reads and no staleness/integrity check joins against. The event
|
|
276
|
+
* stream is where every other lifecycle fact already lives, so the outcome goes
|
|
277
|
+
* there too — carrying IDENTITY (`session_id` raw UUID + the record id the
|
|
278
|
+
* backfill acted on + the attested semantic id), ACTION, and, when the action
|
|
279
|
+
* was an error, its REASON. The side-log is unchanged and remains the verbose
|
|
280
|
+
* copy; this is the queryable one.
|
|
281
|
+
*
|
|
282
|
+
* `kind` distinguishes the two backfill classes, which share the log file and
|
|
283
|
+
* would otherwise be indistinguishable in aggregate: `'abandoned'`
|
|
284
|
+
* (`backfillAbandonedSession`) vs `'state-md-completed'`
|
|
285
|
+
* (`backfillCompletedFromStateMd`).
|
|
286
|
+
*
|
|
287
|
+
* Every id key is OMITTED when unknown — never `""`, never a guess (#1068 AC1).
|
|
288
|
+
* Best-effort throughout: observability must never block teardown.
|
|
289
|
+
*
|
|
290
|
+
* @param {'abandoned'|'state-md-completed'} kind
|
|
291
|
+
* @param {{action?: string, sessionId?: string|null, supersedes?: string, error?: string}|null|undefined} result
|
|
292
|
+
* @param {{sessionId: string|null, semanticSessionId: string|null}} ids
|
|
293
|
+
* @returns {Promise<void>}
|
|
294
|
+
*/
|
|
295
|
+
async function emitBackfillOutcome(kind, result, { sessionId, semanticSessionId }) {
|
|
296
|
+
try {
|
|
297
|
+
const recordId = typeof result?.sessionId === 'string' ? result.sessionId : null;
|
|
298
|
+
await emitEvent('orchestrator.session.backfill_completed', {
|
|
299
|
+
kind,
|
|
300
|
+
action: typeof result?.action === 'string' ? result.action : 'unknown',
|
|
301
|
+
...(sessionId !== null ? { session_id: sessionId } : {}),
|
|
302
|
+
...(semanticSessionId !== null ? { semantic_session_id: semanticSessionId } : {}),
|
|
303
|
+
...(recordId !== null ? { record_id: recordId } : {}),
|
|
304
|
+
...(typeof result?.supersedes === 'string' ? { supersedes: result.supersedes } : {}),
|
|
305
|
+
...(typeof result?.error === 'string' ? { reason: result.error } : {}),
|
|
306
|
+
});
|
|
307
|
+
} catch { /* observability is best-effort */ }
|
|
308
|
+
}
|
|
309
|
+
|
|
141
310
|
// ---------------------------------------------------------------------------
|
|
142
311
|
// main
|
|
143
312
|
// ---------------------------------------------------------------------------
|
|
@@ -152,8 +321,20 @@ async function main() {
|
|
|
152
321
|
|
|
153
322
|
// Single emission path: emitEvent writes the canonical {timestamp, event, ...payload}
|
|
154
323
|
// JSONL record AND fires the optional Clank webhook with the SAME event name.
|
|
324
|
+
//
|
|
325
|
+
// #1068 AC1 — the terminal event carries the raw UUID *and* the semantic id
|
|
326
|
+
// whenever the latter is ATTESTED, so a lifecycle outcome is joinable by
|
|
327
|
+
// identity from events.jsonl alone (previously only `session.lock.acquired`
|
|
328
|
+
// carried both, and only ~1/3 of sessions emit one). The key is OMITTED, never
|
|
329
|
+
// written as `""` or `null`, when `resolveSession()` could not attest it —
|
|
330
|
+
// "identity unresolved" must stay visibly unresolved rather than become a
|
|
331
|
+
// guessed id (#1068 AC1's explicit "niemals eine geratene ID"). Note the
|
|
332
|
+
// attestation bar is the #863 defect (c) guard inside `resolveSession()`: an
|
|
333
|
+
// ending session that is NOT the one current-session.json describes resolves
|
|
334
|
+
// `semanticSessionId: null` and therefore emits no key here.
|
|
155
335
|
await emitEvent('orchestrator.session.ended', {
|
|
156
336
|
...(sessionId !== null ? { session_id: sessionId } : {}),
|
|
337
|
+
...(semanticSessionId !== null ? { semantic_session_id: semanticSessionId } : {}),
|
|
157
338
|
reason,
|
|
158
339
|
duration_ms: durationMs,
|
|
159
340
|
});
|
|
@@ -172,7 +353,21 @@ async function main() {
|
|
|
172
353
|
// historical migration CLI) is NEVER passed here: a foreign lock that is live
|
|
173
354
|
// at hook-time is, by definition, a real active session, not stale history.
|
|
174
355
|
try {
|
|
175
|
-
await backfillAbandonedSession({ repoRoot: projectRoot, sessionId, semanticSessionId });
|
|
356
|
+
const res = await backfillAbandonedSession({ repoRoot: projectRoot, sessionId, semanticSessionId });
|
|
357
|
+
await emitBackfillOutcome('abandoned', res, { sessionId, semanticSessionId });
|
|
358
|
+
} catch { /* best-effort — never block teardown */ }
|
|
359
|
+
|
|
360
|
+
// (a2) #429 — STATE.md `status: completed` self-heal. Orthogonal to (a)
|
|
361
|
+
// above and to THIS session's own id: it reads STATE.md directly and
|
|
362
|
+
// repairs a PAST session whose `status: completed` was set (by hand or
|
|
363
|
+
// otherwise) without session-end's Phase 3.7 ever writing the matching
|
|
364
|
+
// sessions.jsonl record — the exact state `commands/close.md`'s
|
|
365
|
+
// Pre-Check then reads as "already finalized" forever after. Cheap
|
|
366
|
+
// no-op on the overwhelmingly common path (STATE.md status is
|
|
367
|
+
// 'active'/'paused'/'idle', or the record already exists).
|
|
368
|
+
try {
|
|
369
|
+
const res = await backfillCompletedFromStateMd({ repoRoot: projectRoot });
|
|
370
|
+
await emitBackfillOutcome('state-md-completed', res, { sessionId, semanticSessionId });
|
|
176
371
|
} catch { /* best-effort — never block teardown */ }
|
|
177
372
|
|
|
178
373
|
// (b) Deterministic lock release — ONLY a lock whose raw/native session_id
|
|
@@ -276,7 +471,7 @@ async function main() {
|
|
|
276
471
|
await emitEvent('orchestrator.session.lock.released', {
|
|
277
472
|
session_id: sessionId,
|
|
278
473
|
lock_session_id: lock.session_id,
|
|
279
|
-
semantic_session_id: semanticSessionId,
|
|
474
|
+
...(semanticSessionId !== null ? { semantic_session_id: semanticSessionId } : {}),
|
|
280
475
|
end_reason: reason,
|
|
281
476
|
caller: 'on-session-end',
|
|
282
477
|
outcome: benignAlreadyGone ? 'already-gone' : 'deleted',
|
|
@@ -348,6 +543,12 @@ async function main() {
|
|
|
348
543
|
logSweepEvent({ event: 'deregister-failed', session_id: sessionId, error: err?.message ?? String(err) });
|
|
349
544
|
}
|
|
350
545
|
}
|
|
546
|
+
|
|
547
|
+
// (d) #1138 — mechanical telemetry flush. Deliberately LAST: it is the only
|
|
548
|
+
// step here that may touch the network, so every local-fs guarantee above
|
|
549
|
+
// (backfill, lock release, deregistration) is already durable before the
|
|
550
|
+
// hook spends any of its remaining budget on a POST.
|
|
551
|
+
await flushTelemetry(projectRoot);
|
|
351
552
|
}
|
|
352
553
|
|
|
353
554
|
// Exit 0 always — informational hook must never block session teardown.
|