claude-code-session-manager 0.75.3 → 0.77.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/dist/assets/{AgentLibrary-CzQqcObq.js → AgentLibrary-B2ie8bbw.js} +2 -2
- package/dist/assets/{DataModel-Bj_WlLz8.js → DataModel-BIJPYw32.js} +1 -1
- package/dist/assets/{History-DnSi_OHm.js → History-CeY6dk9S.js} +2 -2
- package/dist/assets/{Hooks-0BB0dp3S.js → Hooks-BFH2ocKg.js} +2 -2
- package/dist/assets/{HostBilko-DHpwwsLQ.js → HostBilko-36gj9wLz.js} +1 -1
- package/dist/assets/{Library-CaJVqVvi.js → Library-C-hBct39.js} +1 -1
- package/dist/assets/{ListDetail-C1W2HmC2.js → ListDetail-CNq64VWV.js} +1 -1
- package/dist/assets/{MarkdownEditor-5Ob9FW3z.js → MarkdownEditor-Bh3qt5-1.js} +1 -1
- package/dist/assets/{McpServers-JxCSfm1S.js → McpServers-DpGN0oyz.js} +1 -1
- package/dist/assets/{Memory-BDeqlqwH.js → Memory-D59hUjC4.js} +6 -6
- package/dist/assets/{Panel-Dh9ZHuEj.js → Panel-DCgbaoci.js} +1 -1
- package/dist/assets/{Permissions-DXy-CbEY.js → Permissions-DAmQ0DYV.js} +2 -2
- package/dist/assets/{Plugins-_n1Iuc8T.js → Plugins-Dyfgn6Is.js} +2 -2
- package/dist/assets/{ProvenanceBadge-BP_evfxE.js → ProvenanceBadge-BiYhPO1U.js} +1 -1
- package/dist/assets/SaveBar-RV7B6sOh.js +1 -0
- package/dist/assets/Scheduler-BPaNqx1b.js +14 -0
- package/dist/assets/{ScopeSwitcher-CAWzM6RI.js → ScopeSwitcher-P4mdLGNU.js} +1 -1
- package/dist/assets/{Settings-DRRozLyT.js → Settings-BL4vf5aX.js} +1 -1
- package/dist/assets/{SkillReferenceGraph-DGHDWlz4.js → SkillReferenceGraph-BRBDyi1_.js} +1 -1
- package/dist/assets/{Skills-D8L66eiX.js → Skills-BV08gDUH.js} +2 -2
- package/dist/assets/{SystemPrompt-CYtUsonD.js → SystemPrompt-CLftSsDw.js} +1 -1
- package/dist/assets/TagLibrary-Bp8jGsd5.js +1 -0
- package/dist/assets/{TiptapBody-B2hRgbPE.js → TiptapBody-jCpuB6E5.js} +1 -1
- package/dist/assets/{Toggle-BTwsbxam.js → Toggle-D2paA1xf.js} +1 -1
- package/dist/assets/{index-DijufvkJ.js → index-BDRSqBl3.js} +704 -704
- package/dist/assets/{index-CMLnzdZC.css → index-CYhdtisq.css} +1 -1
- package/dist/assets/{settingsSchema-D6wzxAi6.js → settingsSchema-6IOLjZZN.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +8 -2
- package/plugins/session-manager-dev/skills/develop/standards.md +1 -1
- package/scripts/lib/activeSessions.cjs +116 -6
- package/scripts/project-pages-logic/dist/logic.cjs +4709 -0
- package/scripts/render-project-pages/dist/renderer.cjs +18900 -0
- package/scripts/render-project-pages.cjs +70 -0
- package/scripts/scheduler-mcp-server.cjs +269 -96
- package/scripts/validate-project-pages-summary.cjs +62 -0
- package/src/main/__tests__/agentModelResolve.test.cjs +66 -0
- package/src/main/__tests__/epicStatusMirror.test.cjs +110 -0
- package/src/main/__tests__/health-delegation-chain.test.cjs +106 -0
- package/src/main/__tests__/prdAdminRoutes.test.cjs +295 -0
- package/src/main/__tests__/prdAgentType.test.cjs +103 -0
- package/src/main/__tests__/prdCreate.test.cjs +247 -0
- package/src/main/__tests__/prdFrontmatterAgentType.test.cjs +117 -0
- package/src/main/__tests__/prdFrontmatterQuietMachine.test.cjs +108 -0
- package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +485 -0
- package/src/main/__tests__/projectPages.test.cjs +73 -1
- package/src/main/__tests__/rcaReport.test.cjs +54 -0
- package/src/main/__tests__/runVerify.test.cjs +94 -0
- package/src/main/__tests__/scheduler-autofix-select.test.cjs +58 -3
- package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +103 -0
- package/src/main/__tests__/scheduler-commit-guard-noop.test.cjs +41 -0
- package/src/main/__tests__/scheduler-effective-concurrency.test.cjs +10 -0
- package/src/main/__tests__/scheduler-foreign-wip-manifest.test.cjs +78 -0
- package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +242 -0
- package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +31 -0
- package/src/main/__tests__/scheduler-launch-failure.test.cjs +201 -0
- package/src/main/__tests__/scheduler-leftover-fields.test.cjs +52 -0
- package/src/main/__tests__/scheduler-looks-done.test.cjs +241 -0
- package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +135 -0
- package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +222 -0
- package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +207 -1
- package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +212 -0
- package/src/main/__tests__/scheduler-stranded-investigation.test.cjs +185 -0
- package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +194 -0
- package/src/main/__tests__/seedAgentPersonas.test.cjs +75 -14
- package/src/main/__tests__/seedSchedulerMcp.test.cjs +66 -0
- package/src/main/__tests__/uniquePrdNumbers.test.cjs +14 -5
- package/src/main/bilkoHost.cjs +4 -3
- package/src/main/chatRunner.cjs +6 -1
- package/src/main/config.cjs +25 -33
- package/src/main/health.cjs +153 -2
- package/src/main/index.cjs +64 -5
- package/src/main/ipcSchemas.cjs +69 -1
- package/src/main/lib/__tests__/activeIndexRebuild.test.cjs +179 -0
- package/src/main/lib/__tests__/childWithLog.test.cjs +141 -0
- package/src/main/lib/__tests__/delegationReadiness.test.cjs +391 -42
- package/src/main/lib/__tests__/ephemeralCwd.test.cjs +91 -0
- package/src/main/lib/__tests__/epicWorktreeMint.test.cjs +5 -3
- package/src/main/lib/__tests__/fixChainDepth.test.cjs +40 -0
- package/src/main/lib/__tests__/gitWorktree.test.cjs +290 -5
- package/src/main/lib/__tests__/gitWorktreeSalvage.test.cjs +107 -0
- package/src/main/lib/__tests__/gitWorktreeSalvageDelta.test.cjs +153 -0
- package/src/main/lib/__tests__/jobWorktree.test.cjs +6 -4
- package/src/main/lib/__tests__/landedSinceRun.test.cjs +73 -0
- package/src/main/lib/__tests__/launchFailure.test.cjs +220 -0
- package/src/main/lib/__tests__/loadGate.test.cjs +159 -0
- package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +102 -0
- package/src/main/lib/__tests__/opsOwnership.test.cjs +7 -0
- package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +151 -0
- package/src/main/lib/__tests__/opsRootResolve.test.cjs +149 -0
- package/src/main/lib/__tests__/prdDeclaredPaths.test.cjs +82 -0
- package/src/main/lib/__tests__/projectRootResolve.test.cjs +148 -0
- package/src/main/lib/__tests__/queueHealth.test.cjs +58 -0
- package/src/main/lib/__tests__/quietMachineLease.test.cjs +39 -0
- package/src/main/lib/__tests__/reaperHelpers.test.cjs +133 -0
- package/src/main/lib/__tests__/schedulerBatchDepends.test.cjs +19 -9
- package/src/main/lib/__tests__/schedulerBatchFairness.test.cjs +213 -0
- package/src/main/lib/__tests__/schedulerBatchLaunchHold.test.cjs +125 -0
- package/src/main/lib/__tests__/schedulerBatchProjectCap.test.cjs +127 -0
- package/src/main/lib/__tests__/schedulerBatchQuietMachine.test.cjs +109 -0
- package/src/main/lib/__tests__/schedulerMcpServerHeadlessRefusal.test.cjs +71 -0
- package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +217 -0
- package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +350 -0
- package/src/main/lib/activeIndexMerge.cjs +15 -0
- package/src/main/lib/activeIndexRebuild.cjs +133 -0
- package/src/main/lib/agentModelResolve.cjs +58 -0
- package/src/main/lib/buildTarget.cjs +3 -2
- package/src/main/lib/childWithLog.cjs +69 -2
- package/src/main/lib/claudeBin.cjs +54 -1
- package/src/main/lib/crossProjectFeedback.cjs +8 -1
- package/src/main/lib/definitionOfDone.cjs +3 -2
- package/src/main/lib/delegationReadiness.cjs +514 -26
- package/src/main/lib/ephemeralCwd.cjs +78 -0
- package/src/main/lib/epicDelegationStats.cjs +2 -1
- package/src/main/lib/epicMint.cjs +17 -1
- package/src/main/lib/epicStatusMirror.cjs +95 -0
- package/src/main/lib/epicValidationHook.cjs +2 -1
- package/src/main/lib/epicWorktreeMint.cjs +5 -2
- package/src/main/lib/fixChainDepth.cjs +45 -0
- package/src/main/lib/gitWorktree.cjs +520 -21
- package/src/main/lib/jobWorktree.cjs +2 -0
- package/src/main/lib/landedSinceRun.cjs +55 -0
- package/src/main/lib/launchFailure.cjs +357 -0
- package/src/main/lib/loadGate.cjs +134 -0
- package/src/main/lib/mcpToolCatalog.cjs +370 -0
- package/src/main/lib/opsErrorLog.cjs +12 -1
- package/src/main/lib/opsOwnership.cjs +106 -0
- package/src/main/lib/prdAdminRoutes.cjs +43 -3
- package/src/main/lib/prdAgentType.cjs +84 -0
- package/src/main/lib/prdCreate.cjs +103 -15
- package/src/main/lib/prdDeclaredPaths.cjs +70 -0
- package/src/main/lib/prdFrontmatter.cjs +17 -3
- package/src/main/lib/prdLocations.cjs +13 -6
- package/src/main/lib/projectHomeAdminRoutes.cjs +402 -0
- package/src/main/lib/projectPageSummarySchema.cjs +181 -0
- package/src/main/lib/projectRootResolve.cjs +134 -0
- package/src/main/lib/promptSessionSchema.cjs +7 -0
- package/src/main/lib/queueHealth.cjs +38 -0
- package/src/main/lib/queueStore.cjs +40 -7
- package/src/main/lib/quietMachineLease.cjs +48 -0
- package/src/main/lib/rcaReport.cjs +54 -4
- package/src/main/lib/reaperHelpers.cjs +64 -1
- package/src/main/lib/scheduleJobSchema.cjs +31 -0
- package/src/main/lib/scheduleJobTransitions.cjs +6 -2
- package/src/main/lib/schedulerBatch.cjs +301 -55
- package/src/main/lib/schedulerConfig.cjs +99 -0
- package/src/main/projectBrief.cjs +3 -2
- package/src/main/projectPages.cjs +162 -3
- package/src/main/promptSessionTranscript.cjs +0 -0
- package/src/main/pty.cjs +5 -0
- package/src/main/queueOps.cjs +15 -8
- package/src/main/runVerify.cjs +50 -9
- package/src/main/scheduler/prdParser.cjs +18 -1
- package/src/main/scheduler.cjs +1701 -130
- package/src/main/seedAgentPersonas.cjs +62 -21
- package/src/main/seedSchedulerMcp.cjs +58 -4
- package/src/main/templates/project-pages-catalog.json +741 -0
- package/src/main/templates/project-pages-pipeline.md +417 -0
- package/src/preload/api.d.ts +187 -3
- package/src/preload/index.cjs +9 -0
- package/src/seed/agents/project-home-builder.md +59 -0
- package/dist/assets/SaveBar-D-gCUx4n.js +0 -1
- package/dist/assets/Scheduler-Bpd4OGju.js +0 -14
- package/dist/assets/TagLibrary-E5CLeuVk.js +0 -1
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* prdAgentType.cjs — the PRD-frontmatter `agentType` FK, mirroring
|
|
5
|
+
* agentModelResolve.cjs's/epicMint.cjs's established convention for the
|
|
6
|
+
* Epic-level `agentType` FK: THROW ON WRITE, REPORT ON READ.
|
|
7
|
+
*
|
|
8
|
+
* A PRD's `agentType` names the persona (`~/.claude/agents/<name>.md`, or a
|
|
9
|
+
* project overlay at `<cwd>/.claude/agents/<name>.md`) that should execute
|
|
10
|
+
* it — distinct from `tag` (workTypeLibrary.cjs), which is the WORK TYPE.
|
|
11
|
+
* Reuses epicMint.cjs's personaFileExists/resolvePersonaPaths rather than a
|
|
12
|
+
* second persona-file reader, and agentLibrary.cjs's listPersonas() for the
|
|
13
|
+
* "available personas" name list surfaced in the write-time rejection error.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const { personaFileExists, resolvePersonaPaths } = require('./epicMint.cjs');
|
|
17
|
+
const { listPersonas } = require('../agentLibrary.cjs');
|
|
18
|
+
|
|
19
|
+
/** PRD execution's documented default persona (dev-lead.md's own frontmatter role). */
|
|
20
|
+
const DEFAULT_PRD_AGENT_TYPE = 'dev-lead';
|
|
21
|
+
|
|
22
|
+
// READ side of the FK — logged at most once per (cwd, agentType) so a hot
|
|
23
|
+
// reconcile/list pass re-parsing the same on-disk PRD every tick doesn't
|
|
24
|
+
// flood opsErrorLog's daily file. Mirrors agentModelResolve.cjs's
|
|
25
|
+
// loggedDanglingPersonas set.
|
|
26
|
+
const loggedDanglingAgentTypes = new Set();
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Read-time tolerance check: a PRD on disk whose persona was renamed or
|
|
30
|
+
* deleted after the PRD was written must still load. Never throws — logs at
|
|
31
|
+
* most once via opsErrorLog when `agentType` is set but unresolvable.
|
|
32
|
+
*/
|
|
33
|
+
function reportDanglingAgentTypeOnce(cwd, agentType, deps = {}) {
|
|
34
|
+
if (!cwd || !agentType) return;
|
|
35
|
+
try {
|
|
36
|
+
const checkPersonaExists = deps.personaExists || personaFileExists;
|
|
37
|
+
if (checkPersonaExists(cwd, agentType, deps)) return;
|
|
38
|
+
const key = `${cwd}::${agentType}`;
|
|
39
|
+
if (loggedDanglingAgentTypes.has(key)) return;
|
|
40
|
+
loggedDanglingAgentTypes.add(key);
|
|
41
|
+
// Required lazily, same rationale as agentModelResolve.cjs's
|
|
42
|
+
// logDanglingPersonaOnce: pay for this leaf module only on the (rare)
|
|
43
|
+
// miss path.
|
|
44
|
+
const { appendError } = require('./opsErrorLog.cjs');
|
|
45
|
+
appendError({
|
|
46
|
+
cwd,
|
|
47
|
+
scope: 'prdAgentType',
|
|
48
|
+
level: 'warn',
|
|
49
|
+
message: `PRD agentType '${agentType}' has no resolvable persona file — PRD still loads`,
|
|
50
|
+
meta: { agentType },
|
|
51
|
+
});
|
|
52
|
+
} catch { /* logging must never break PRD loading */ }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Write-time FK check: throws when `agentType` is supplied but doesn't
|
|
57
|
+
* resolve to a readable persona file, naming the available personas (from
|
|
58
|
+
* the Agent Library) so the caller can pick a real one. A no-op when
|
|
59
|
+
* `agentType` is falsy — the caller is expected to default it separately
|
|
60
|
+
* (buildPrdBody's DEFAULT_PRD_AGENT_TYPE fill-in).
|
|
61
|
+
*/
|
|
62
|
+
async function assertAgentTypeWritable(cwd, agentType, deps = {}) {
|
|
63
|
+
if (!agentType) return;
|
|
64
|
+
const checkPersonaExists = deps.personaExists || personaFileExists;
|
|
65
|
+
if (checkPersonaExists(cwd, agentType, deps)) return;
|
|
66
|
+
|
|
67
|
+
const load = deps.listPersonas || listPersonas;
|
|
68
|
+
let available = [];
|
|
69
|
+
try {
|
|
70
|
+
available = (await load()).map((p) => p.name).filter(Boolean).sort();
|
|
71
|
+
} catch { /* best-effort — still throw below even if listing failed */ }
|
|
72
|
+
|
|
73
|
+
const { projectPath, globalPath } = resolvePersonaPaths(cwd, agentType, deps);
|
|
74
|
+
throw new Error(
|
|
75
|
+
`agentType '${agentType}' does not resolve to a readable persona file — checked ${projectPath} and `
|
|
76
|
+
+ `${globalPath}. Available personas: ${available.length ? available.join(', ') : '(none found)'}`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
DEFAULT_PRD_AGENT_TYPE,
|
|
82
|
+
assertAgentTypeWritable,
|
|
83
|
+
reportDanglingAgentTypeOnce,
|
|
84
|
+
};
|
|
@@ -25,6 +25,19 @@ const { expandHome } = require('./expandHome.cjs');
|
|
|
25
25
|
const { readBody, sendJson } = require('./localAdminHttp.cjs');
|
|
26
26
|
const { readActiveIndex } = require('./epicMint.cjs');
|
|
27
27
|
const { appendAuditEvent } = require('./auditLog.cjs');
|
|
28
|
+
const { resolveProjectContext } = require('./projectRootResolve.cjs');
|
|
29
|
+
const { fixChainDepthOf, baseSlugOf } = require('./fixChainDepth.cjs');
|
|
30
|
+
const { DEFAULT_PRD_AGENT_TYPE, assertAgentTypeWritable } = require('./prdAgentType.cjs');
|
|
31
|
+
|
|
32
|
+
// Fix-chain depth cap (PRD 1113): a fix-of-a-fix (depth >= 2) is refused at
|
|
33
|
+
// this shared write path rather than caught later — scheduler.cjs's
|
|
34
|
+
// MAX_INVESTIGATION_DEPTH=1 caps the auto-fix loop's OWN recursion the same
|
|
35
|
+
// way, so the two limits must stay numerically consistent (depth 1 allowed,
|
|
36
|
+
// depth 2 refused). This guard exists because that auto-fix cap only ever
|
|
37
|
+
// sees PRDs `spawnInvestigation` itself authors; a human or planner session
|
|
38
|
+
// authoring a `*-fix-fix-*` slug through this API bypasses it entirely —
|
|
39
|
+
// which is exactly what happened on starry-night-ships (see PRD 1113's Goal).
|
|
40
|
+
const FIX_CHAIN_DEPTH_CAP = 1;
|
|
28
41
|
|
|
29
42
|
const STANDARDS_PATH = path.join(
|
|
30
43
|
__dirname, '..', '..', '..',
|
|
@@ -50,7 +63,7 @@ function deriveSlugFromTitle(title) {
|
|
|
50
63
|
function buildPrdBody(input) {
|
|
51
64
|
const {
|
|
52
65
|
title, cwd, estimateMinutes, goal, acceptanceCriteria,
|
|
53
|
-
implementationNotes, outOfScope, sourcePromptId, sourceTabId, tag, dependsOn,
|
|
66
|
+
implementationNotes, outOfScope, sourcePromptId, sourceTabId, tag, agentType, dependsOn, quietMachine,
|
|
54
67
|
} = input;
|
|
55
68
|
|
|
56
69
|
// No `parallelGroup` frontmatter key by convention (SKILL.md) — the NN-
|
|
@@ -75,8 +88,21 @@ function buildPrdBody(input) {
|
|
|
75
88
|
// Optional, additive: the user-selected Feature/Bug tag (PRD 774) carried
|
|
76
89
|
// through from the originating PromptTicket — deterministic, never LLM-classified.
|
|
77
90
|
if (tag) fmLines.push(`tag: ${tag}`);
|
|
91
|
+
// WHO executes this PRD — distinct from `tag` (the WORK TYPE, above).
|
|
92
|
+
// Always emitted (unlike tag): a PRD with no agentType is ambiguous about
|
|
93
|
+
// who runs it, so createPrd() fills in DEFAULT_PRD_AGENT_TYPE ('dev-lead')
|
|
94
|
+
// before calling this function when the caller omitted one. Validated at
|
|
95
|
+
// write time by createPrd() via prdAgentType.cjs's assertAgentTypeWritable
|
|
96
|
+
// — this function only serializes, it never validates.
|
|
97
|
+
fmLines.push(`agentType: ${agentType || DEFAULT_PRD_AGENT_TYPE}`);
|
|
78
98
|
// Explicit ordering (PRD 832): replaces the retired shared-NN convention.
|
|
79
99
|
if (dependsOn && dependsOn.length) fmLines.push(`dependsOn: [${dependsOn.join(', ')}]`);
|
|
100
|
+
// Opt-in exclusive-lease flag (PRD 1107): serializes this job against
|
|
101
|
+
// every other job machine-wide for its run, for a PRD whose acceptance
|
|
102
|
+
// criteria are wall-clock/timing measurements that CPU contention from
|
|
103
|
+
// sibling jobs would otherwise invalidate. Only emitted when true — see
|
|
104
|
+
// prdFrontmatter.cjs's applyKey for the matching "only true opts in" parse.
|
|
105
|
+
if (quietMachine === true) fmLines.push('quietMachine: true');
|
|
80
106
|
fmLines.push('---', '');
|
|
81
107
|
|
|
82
108
|
const acLines = acceptanceCriteria.map((line) => `- [ ] ${line}`).join('\n');
|
|
@@ -138,17 +164,41 @@ function resolveSourcePromptIdFromClaudeSession(cwd, claudeSessionId) {
|
|
|
138
164
|
* (400/409/500), left to the caller since only the HTTP route needs it.
|
|
139
165
|
*/
|
|
140
166
|
async function createPrd(input, remote) {
|
|
141
|
-
// cwd is untrusted
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
167
|
+
// cwd is untrusted AND, since this PRD, optional — a caller inside an
|
|
168
|
+
// Epic's git worktree naturally passes its worktree pwd (or omits cwd
|
|
169
|
+
// entirely and relies on originClaudeSessionId/sourcePromptId). Resolve the
|
|
170
|
+
// real project + owning Epic server-side BEFORE any validation: a worktree
|
|
171
|
+
// cwd currently PASSES config.validatePath and then reads the WRONG
|
|
172
|
+
// active-index.json (a git-tracked snapshot frozen at branch time), which
|
|
173
|
+
// is why "the Epic got lost" reports keep recurring even though the Epic
|
|
174
|
+
// never left main's index. See projectRootResolve.cjs's header.
|
|
175
|
+
const rawCwd = input.cwd ? expandHome(input.cwd) : null;
|
|
176
|
+
const originProjectRoot = input.originProjectRoot ? expandHome(input.originProjectRoot) : null;
|
|
177
|
+
const resolved = resolveProjectContext({
|
|
178
|
+
cwd: rawCwd,
|
|
179
|
+
originClaudeSessionId: input.originClaudeSessionId,
|
|
180
|
+
// Only ask the resolver to hunt for the project BY Epic id when no cwd
|
|
181
|
+
// (nor a trusted SM_PROJECT_ROOT hint) was supplied at all — an explicit
|
|
182
|
+
// cwd that merely needs worktree/ops-path normalization is already fixed
|
|
183
|
+
// by projectRootOf above, so the common already-correct-cwd call (cwd +
|
|
184
|
+
// sourcePromptId both supplied) never pays for a cross-project scan.
|
|
185
|
+
epicId: (rawCwd || originProjectRoot) ? undefined : input.sourcePromptId,
|
|
186
|
+
originProjectRoot,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (!resolved.cwd) {
|
|
190
|
+
return {
|
|
191
|
+
ok: false,
|
|
192
|
+
status: 400,
|
|
193
|
+
error: 'cwd is required — pass cwd explicitly, or originClaudeSessionId/sourcePromptId that resolves '
|
|
194
|
+
+ 'to an existing, already-approved Epic (create/approve an Epic first)',
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// A resolved Epic's own project cwd always wins over the supplied/hinted
|
|
199
|
+
// one — see resolveProjectContext's header. `cwd` below is that final,
|
|
200
|
+
// already-normalized (worktree -> main tree) value.
|
|
201
|
+
const cwd = resolved.cwd;
|
|
152
202
|
let realCwd;
|
|
153
203
|
try {
|
|
154
204
|
realCwd = config.validatePath(cwd);
|
|
@@ -164,9 +214,16 @@ async function createPrd(input, remote) {
|
|
|
164
214
|
config.addAllowedRoot(realCwd);
|
|
165
215
|
input = { ...input, cwd };
|
|
166
216
|
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
|
|
217
|
+
if (resolved.epicId) {
|
|
218
|
+
input = { ...input, sourcePromptId: resolved.epicId };
|
|
219
|
+
} else if (!input.sourcePromptId && input.originClaudeSessionId) {
|
|
220
|
+
// Fallback kept for the narrow case a caller passes originClaudeSessionId
|
|
221
|
+
// but resolveProjectContext (which also scans cross-project) somehow
|
|
222
|
+
// missed it — resolveSourcePromptIdFromClaudeSession is the single-cwd
|
|
223
|
+
// primitive resolveProjectContext itself is built from, so this can only
|
|
224
|
+
// ever be a no-op in practice; kept for defense-in-depth, not duplicated logic.
|
|
225
|
+
const fallback = resolveSourcePromptIdFromClaudeSession(cwd, input.originClaudeSessionId);
|
|
226
|
+
if (fallback) input = { ...input, sourcePromptId: fallback };
|
|
170
227
|
}
|
|
171
228
|
|
|
172
229
|
const slug = input.slug || deriveSlugFromTitle(input.title);
|
|
@@ -185,6 +242,23 @@ async function createPrd(input, remote) {
|
|
|
185
242
|
const nn = await remote.allocateParallelGroup(input.cwd);
|
|
186
243
|
const filenameSlug = `${nn}-${slug}`;
|
|
187
244
|
|
|
245
|
+
// Fix-chain depth guard (PRD 1113): refuse a fix-of-a-fix before it's ever
|
|
246
|
+
// written. depth 0/1 pass through unchanged (an ordinary PRD, or a first
|
|
247
|
+
// fix-plan, is never blocked); depth >= 2 means the base job has already
|
|
248
|
+
// failed to close via at least one prior fix attempt for reasons a repeat
|
|
249
|
+
// attempt won't resolve.
|
|
250
|
+
const chainDepth = fixChainDepthOf(filenameSlug);
|
|
251
|
+
if (chainDepth > FIX_CHAIN_DEPTH_CAP) {
|
|
252
|
+
const base = baseSlugOf(filenameSlug);
|
|
253
|
+
return {
|
|
254
|
+
ok: false,
|
|
255
|
+
status: 409,
|
|
256
|
+
error: `Fix-chain depth cap exceeded: "${base}" has already failed to close ${chainDepth} time(s) for ` +
|
|
257
|
+
`infrastructure reasons (refusing to write "${filenameSlug}.md" at depth ${chainDepth}). Authoring a ` +
|
|
258
|
+
'deeper fix PRD is not the correct response — stop and surface this to a human instead of retrying again.',
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
188
262
|
// An explicit `parallelGroup` bypasses allocateParallelGroup()'s
|
|
189
263
|
// collision-proof reservation, so re-check for an existing file at
|
|
190
264
|
// this exact destination before writing — remote.writePrd itself has
|
|
@@ -196,6 +270,20 @@ async function createPrd(input, remote) {
|
|
|
196
270
|
return { ok: false, status: 409, error: `PRD already exists: ${filenameSlug}.md` };
|
|
197
271
|
}
|
|
198
272
|
|
|
273
|
+
// Write-time FK check (throw on write, report on read — see
|
|
274
|
+
// prdAgentType.cjs's header): a caller-supplied agentType that doesn't
|
|
275
|
+
// resolve to a readable persona file is rejected before anything is
|
|
276
|
+
// written, naming the available personas. A no-op when agentType is
|
|
277
|
+
// omitted — buildPrdBody fills in DEFAULT_PRD_AGENT_TYPE for that case,
|
|
278
|
+
// and the default persona is trusted without a lookup.
|
|
279
|
+
if (input.agentType) {
|
|
280
|
+
try {
|
|
281
|
+
await assertAgentTypeWritable(input.cwd, input.agentType);
|
|
282
|
+
} catch (e) {
|
|
283
|
+
return { ok: false, status: 400, error: e?.message ?? 'invalid agentType' };
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
199
287
|
const body = buildPrdBody(input);
|
|
200
288
|
const writeResult = await remote.writePrd(filenameSlug, body, input.cwd);
|
|
201
289
|
if (!writeResult?.ok) {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* prdDeclaredPaths.cjs — derive the file paths a PRD declares it touches, for
|
|
5
|
+
* the reverify self-heal pass's widened evidence window (PRD 1102).
|
|
6
|
+
*
|
|
7
|
+
* Reuses definitionOfDone.cjs's existing `extractSection` (already the PRD
|
|
8
|
+
* markdown section parser used by flagRiskySurfaces) rather than
|
|
9
|
+
* re-implementing one — see standards.md's API-reuse rule.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('node:fs');
|
|
13
|
+
const { splitFrontmatter } = require('./prdFrontmatter.cjs');
|
|
14
|
+
const { extractSection } = require('./definitionOfDone.cjs');
|
|
15
|
+
|
|
16
|
+
// Backtick-quoted, extension-bearing relative paths, e.g. `src/main/scheduler.cjs`
|
|
17
|
+
// or `src/main/scheduler.cjs:490` (trailing :line dropped). Requires at least
|
|
18
|
+
// one `/` so bare identifiers like `npm` or `RESCANNABLE_VERDICTS` never match.
|
|
19
|
+
const PATH_RE = /`([a-zA-Z0-9_][\w.-]*(?:\/[\w.-]+)+\.[a-zA-Z0-9]+)(?::\d+)?`/g;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Extract declared file paths from a PRD body (frontmatter already
|
|
23
|
+
* stripped). Scans the `# Implementation notes` and `# Acceptance criteria`
|
|
24
|
+
* sections only — the same sections flagRiskySurfaces and the PRD-authoring
|
|
25
|
+
* convention already treat as where files-touched are named. Returns `[]`
|
|
26
|
+
* (never fabricates a path) when neither section names one.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} prdBody
|
|
29
|
+
* @returns {string[]} deduped, in first-seen order
|
|
30
|
+
*/
|
|
31
|
+
function extractDeclaredPaths(prdBody) {
|
|
32
|
+
if (!prdBody || typeof prdBody !== 'string') return [];
|
|
33
|
+
const text = [
|
|
34
|
+
extractSection(prdBody, 'Implementation notes'),
|
|
35
|
+
extractSection(prdBody, 'Acceptance criteria'),
|
|
36
|
+
].join('\n');
|
|
37
|
+
|
|
38
|
+
const paths = [];
|
|
39
|
+
const seen = new Set();
|
|
40
|
+
let m;
|
|
41
|
+
PATH_RE.lastIndex = 0;
|
|
42
|
+
while ((m = PATH_RE.exec(text))) {
|
|
43
|
+
if (!seen.has(m[1])) {
|
|
44
|
+
seen.add(m[1]);
|
|
45
|
+
paths.push(m[1]);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return paths;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Read a PRD file and extract its declared paths. Never throws — a missing
|
|
53
|
+
* or unreadable PRD (e.g. archived and swept) resolves to `[]`.
|
|
54
|
+
*
|
|
55
|
+
* @param {string|null} prdPath
|
|
56
|
+
* @returns {string[]}
|
|
57
|
+
*/
|
|
58
|
+
function declaredPathsForPrd(prdPath) {
|
|
59
|
+
if (!prdPath) return [];
|
|
60
|
+
let raw;
|
|
61
|
+
try {
|
|
62
|
+
raw = fs.readFileSync(prdPath, 'utf8');
|
|
63
|
+
} catch {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
const { body } = splitFrontmatter(raw);
|
|
67
|
+
return extractDeclaredPaths(body);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = { extractDeclaredPaths, declaredPathsForPrd };
|
|
@@ -62,7 +62,7 @@ function splitFrontmatter(raw) {
|
|
|
62
62
|
* either surface never reorders or drops a key it didn't touch. Recognized
|
|
63
63
|
* key set intentionally mirrors the TS module exactly (title, cwd,
|
|
64
64
|
* estimateMinutes, parallelGroup, sourcePromptId, sourceTabId, tag,
|
|
65
|
-
* createdVia, issuedAt); every other frontmatter key (e.g. dependsOn)
|
|
65
|
+
* agentType, createdVia, issuedAt); every other frontmatter key (e.g. dependsOn)
|
|
66
66
|
* round-trips via `extras`, same as the renderer's editor today.
|
|
67
67
|
*
|
|
68
68
|
* `createdVia`/`issuedAt` (PRD provenance-lockdown) are recognized here so
|
|
@@ -72,8 +72,8 @@ function splitFrontmatter(raw) {
|
|
|
72
72
|
* into `extras`.
|
|
73
73
|
*/
|
|
74
74
|
|
|
75
|
-
const RECOGNIZED_KEYS = new Set(['title', 'cwd', 'estimateMinutes', 'parallelGroup', 'sourcePromptId', 'sourceTabId', 'tag', 'createdVia', 'issuedAt']);
|
|
76
|
-
const EMIT_ORDER = ['title', 'cwd', 'estimateMinutes', 'parallelGroup', 'sourcePromptId', 'sourceTabId', 'tag', 'createdVia', 'issuedAt'];
|
|
75
|
+
const RECOGNIZED_KEYS = new Set(['title', 'cwd', 'estimateMinutes', 'parallelGroup', 'sourcePromptId', 'sourceTabId', 'tag', 'agentType', 'createdVia', 'issuedAt', 'quietMachine']);
|
|
76
|
+
const EMIT_ORDER = ['title', 'cwd', 'estimateMinutes', 'parallelGroup', 'sourcePromptId', 'sourceTabId', 'tag', 'agentType', 'createdVia', 'issuedAt', 'quietMachine'];
|
|
77
77
|
const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
78
78
|
|
|
79
79
|
function indentOf(line) {
|
|
@@ -125,12 +125,26 @@ function applyKey(fm, key, after) {
|
|
|
125
125
|
// Matches the TS source's applyKey exactly — see PRD_TAG_VALUES above.
|
|
126
126
|
if (PRD_TAG_VALUES.has(v)) fm.tag = v;
|
|
127
127
|
return;
|
|
128
|
+
case 'agentType':
|
|
129
|
+
// WHO executes this PRD — a persona name, not a closed enum (the
|
|
130
|
+
// Agent Library is user-editable). FK resolution against the Agent
|
|
131
|
+
// Library happens in prdAgentType.cjs, not here — this parser only
|
|
132
|
+
// round-trips the string.
|
|
133
|
+
fm.agentType = String(v);
|
|
134
|
+
return;
|
|
128
135
|
case 'createdVia':
|
|
129
136
|
fm.createdVia = String(v);
|
|
130
137
|
return;
|
|
131
138
|
case 'issuedAt':
|
|
132
139
|
fm.issuedAt = String(v);
|
|
133
140
|
return;
|
|
141
|
+
case 'quietMachine':
|
|
142
|
+
// Opt-in exclusive-lease flag (PRD 1107) — only a literal `true`
|
|
143
|
+
// frontmatter value opts in; anything else (including an explicit
|
|
144
|
+
// `false`) is treated as "not set" so the field never round-trips as
|
|
145
|
+
// a no-op line for the overwhelming majority of PRDs that omit it.
|
|
146
|
+
if (v === true) fm.quietMachine = true;
|
|
147
|
+
return;
|
|
134
148
|
}
|
|
135
149
|
}
|
|
136
150
|
|
|
@@ -19,8 +19,14 @@ const fs = require('node:fs');
|
|
|
19
19
|
const path = require('node:path');
|
|
20
20
|
const { activeProjectCwds, allProjectCwds } = require('../../../scripts/lib/activeSessions.cjs');
|
|
21
21
|
|
|
22
|
-
const
|
|
23
|
-
|
|
22
|
+
const { opsPath, OPS_ROOT_DIR } = require('./opsOwnership.cjs');
|
|
23
|
+
|
|
24
|
+
// Segments UNDER the ops root; the ops root itself is resolved by
|
|
25
|
+
// opsOwnership.opsPath (PRD 1082) — never joined onto a raw cwd here.
|
|
26
|
+
const PRD_SUBPATH = ['scheduler', 'prds'];
|
|
27
|
+
const EPICS_SUBPATH = ['scheduler', 'epics'];
|
|
28
|
+
// Segment count from a project root to an epic's own dir: <ops-root> + EPICS_SUBPATH.
|
|
29
|
+
const EPICS_DEPTH_FROM_PROJECT = 1 + EPICS_SUBPATH.length;
|
|
24
30
|
|
|
25
31
|
/**
|
|
26
32
|
* resolveEpicsRoot(cwd) → `<cwd>/session-manager-operations/scheduler/epics`
|
|
@@ -30,7 +36,7 @@ function resolveEpicsRoot(cwd) {
|
|
|
30
36
|
if (!cwd || typeof cwd !== 'string') {
|
|
31
37
|
throw new Error('resolveEpicsRoot: cwd is required');
|
|
32
38
|
}
|
|
33
|
-
return
|
|
39
|
+
return opsPath(cwd, ...EPICS_SUBPATH);
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
/**
|
|
@@ -69,7 +75,8 @@ function listEpicPrdDirs(cwd) {
|
|
|
69
75
|
* its Epic (the layout every new PRD uses) is still found.
|
|
70
76
|
*/
|
|
71
77
|
function listArchivedPrdDirs(cwd) {
|
|
72
|
-
|
|
78
|
+
let dirs;
|
|
79
|
+
try { dirs = [opsPath(cwd, ...PRD_SUBPATH, '..', 'prds-archived')]; } catch { return []; }
|
|
73
80
|
let root;
|
|
74
81
|
try { root = resolveEpicsRoot(cwd); } catch { return dirs; }
|
|
75
82
|
let entries;
|
|
@@ -91,7 +98,7 @@ function resolvePrdWriteDir(cwd) {
|
|
|
91
98
|
if (!cwd || typeof cwd !== 'string') {
|
|
92
99
|
throw new Error('resolvePrdWriteDir: cwd is required');
|
|
93
100
|
}
|
|
94
|
-
return
|
|
101
|
+
return opsPath(cwd, ...PRD_SUBPATH);
|
|
95
102
|
}
|
|
96
103
|
|
|
97
104
|
/**
|
|
@@ -202,7 +209,7 @@ function deriveEpicIdFromPrdPath(filePath) {
|
|
|
202
209
|
const epicId = path.basename(epicDir);
|
|
203
210
|
if (!epicId || epicId === '.' || epicId === path.sep) return null;
|
|
204
211
|
let projectCwd = path.dirname(epicDir); // epicsRoot (.../scheduler/epics)
|
|
205
|
-
for (let i = 0; i <
|
|
212
|
+
for (let i = 0; i < EPICS_DEPTH_FROM_PROJECT; i++) projectCwd = path.dirname(projectCwd);
|
|
206
213
|
let epicsRoot;
|
|
207
214
|
try { epicsRoot = resolveEpicsRoot(projectCwd); } catch { return null; }
|
|
208
215
|
if (path.join(epicsRoot, epicId, dirName) !== prdsDir) return null;
|