peaks-loop 4.0.7 → 4.0.9
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/CHANGELOG.md +58 -288
- package/dist/cli/commands/_register.js +2 -0
- package/dist/cli/commands/baseline-commands.d.ts +3 -0
- package/dist/cli/commands/baseline-commands.js +146 -0
- package/dist/cli/commands/code-job-shape-commands.js +8 -11
- package/dist/cli/commands/code-mode-gate-commands.js +1 -43
- package/dist/cli/commands/code-runtime-commands.js +36 -14
- package/dist/cli/commands/core/skill-command.js +75 -13
- package/dist/cli/commands/core/standards-command.js +5 -50
- package/dist/cli/commands/dispatch-commands.d.ts +20 -0
- package/dist/cli/commands/dispatch-commands.js +39 -38
- package/dist/cli/commands/dispatch-from-dag.js +17 -0
- package/dist/cli/commands/heartbeat-commands.d.ts +21 -0
- package/dist/cli/commands/heartbeat-commands.js +40 -0
- package/dist/cli/commands/hook-handle.js +5 -2
- package/dist/cli/commands/hooks-commands.js +9 -26
- package/dist/cli/commands/loop-eval-commands.js +5 -2
- package/dist/cli/commands/statusline-commands.js +1 -1
- package/dist/cli/commands/sub-agent-commands.js +0 -2
- package/dist/cli/commands/sub-agent-shared.d.ts +7 -9
- package/dist/cli/commands/workflow-commands.js +8 -0
- package/dist/cli/commands/workflow-lifecycle-commands.d.ts +63 -0
- package/dist/cli/commands/workflow-lifecycle-commands.js +302 -0
- package/dist/cli/commands/workspace/init-command.js +19 -0
- package/dist/services/audit/backing-detector.d.ts +2 -19
- package/dist/services/audit/backing-detector.js +7 -66
- package/dist/services/audit/enforcers/active-skill-resolver.d.ts +25 -9
- package/dist/services/audit/enforcers/active-skill-resolver.js +83 -21
- package/dist/services/audit/enforcers/lint-audit-regression.js +1 -10
- package/dist/services/capability-audit-service/cross-check.d.ts +8 -0
- package/dist/services/capability-audit-service/cross-check.js +11 -0
- package/dist/services/capability-audit-service/index.d.ts +3 -0
- package/dist/services/capability-audit-service/index.js +2 -0
- package/dist/services/capability-audit-service/runner.d.ts +27 -0
- package/dist/services/capability-audit-service/runner.js +40 -0
- package/dist/services/capability-audit-service/staleness.d.ts +1 -0
- package/dist/services/capability-audit-service/staleness.js +7 -0
- package/dist/services/capability-audit-service/types.d.ts +24 -0
- package/dist/services/capability-audit-service/types.js +1 -0
- package/dist/services/capability-baseline/store.d.ts +33 -0
- package/dist/services/capability-baseline/store.js +80 -0
- package/dist/services/capability-baseline/types.d.ts +45 -0
- package/dist/services/capability-baseline/types.js +5 -0
- package/dist/services/capability-baseline/validator.d.ts +19 -0
- package/dist/services/capability-baseline/validator.js +38 -0
- package/dist/services/capability-guard-runner/contracts/J01.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J01.js +48 -0
- package/dist/services/capability-guard-runner/contracts/J02.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J02.js +34 -0
- package/dist/services/capability-guard-runner/contracts/J03.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J03.js +24 -0
- package/dist/services/capability-guard-runner/contracts/J04.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J04.js +40 -0
- package/dist/services/capability-guard-runner/contracts/J05.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J05.js +21 -0
- package/dist/services/capability-guard-runner/contracts/J06.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J06.js +43 -0
- package/dist/services/capability-guard-runner/contracts/J07.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J07.js +53 -0
- package/dist/services/capability-guard-runner/contracts/J08.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J08.js +43 -0
- package/dist/services/capability-guard-runner/contracts/J09.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J09.js +44 -0
- package/dist/services/capability-guard-runner/contracts/J10.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J10.js +40 -0
- package/dist/services/capability-guard-runner/contracts/J11.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J11.js +39 -0
- package/dist/services/capability-guard-runner/contracts/J12.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J12.js +34 -0
- package/dist/services/capability-guard-runner/contracts/J13.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J13.js +45 -0
- package/dist/services/capability-guard-runner/contracts/J14.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J14.js +36 -0
- package/dist/services/capability-guard-runner/contracts/J15.d.ts +2 -0
- package/dist/services/capability-guard-runner/contracts/J15.js +40 -0
- package/dist/services/capability-guard-runner/diff.d.ts +2 -0
- package/dist/services/capability-guard-runner/diff.js +7 -0
- package/dist/services/capability-guard-runner/runner.d.ts +9 -0
- package/dist/services/capability-guard-runner/runner.js +22 -0
- package/dist/services/capability-guard-runner/types.d.ts +39 -0
- package/dist/services/capability-guard-runner/types.js +1 -0
- package/dist/services/code/auto-compact-orchestrator.d.ts +35 -2
- package/dist/services/code/auto-compact-orchestrator.js +29 -9
- package/dist/services/context/build-dispatch-system-prompt.js +0 -18
- package/dist/services/dispatch/dispatch-record-writer.d.ts +25 -1
- package/dist/services/dispatch/dispatch-record-writer.js +85 -15
- package/dist/services/dispatch/test-tool-detection.d.ts +0 -14
- package/dist/services/dispatch/test-tool-detection.js +0 -22
- package/dist/services/doctor/doctor-service/checks/skill-presence.d.ts +8 -0
- package/dist/services/doctor/doctor-service/checks/skill-presence.js +9 -1
- package/dist/services/evolution/evolution-types.d.ts +16 -16
- package/dist/services/final-review/final-review-service.d.ts +8 -0
- package/dist/services/final-review/final-review-service.js +14 -0
- package/dist/services/final-review/final-review-types.d.ts +1 -1
- package/dist/services/hooks/presence-marker-detector.d.ts +6 -0
- package/dist/services/hooks/presence-marker-detector.js +48 -0
- package/dist/services/ide/adapters/claude-code-adapter.js +20 -0
- package/dist/services/ide/adapters/codex-adapter.js +20 -1
- package/dist/services/ide/adapters/cursor-adapter.js +21 -1
- package/dist/services/ide/adapters/hermes-adapter.js +20 -1
- package/dist/services/ide/adapters/openclaw-adapter.js +20 -1
- package/dist/services/ide/adapters/qoder-adapter.js +20 -1
- package/dist/services/ide/adapters/tongyi-lingma-adapter.js +20 -1
- package/dist/services/ide/adapters/trae-adapter.js +21 -1
- package/dist/services/ide/adapters/zcode-adapter.js +19 -0
- package/dist/services/ide/ide-types.d.ts +15 -0
- package/dist/services/job/job-progress-store.d.ts +2 -2
- package/dist/services/job/job-types.d.ts +26 -26
- package/dist/services/observability/observability-service.d.ts +4 -4
- package/dist/services/scan/archetype-service.js +2 -52
- package/dist/services/sediment/json-schema.d.ts +4 -4
- package/dist/services/session/caller-binding-service.d.ts +21 -0
- package/dist/services/session/caller-binding-service.js +35 -0
- package/dist/services/session/caller-id-types.d.ts +62 -13
- package/dist/services/session/caller-id-types.js +25 -13
- package/dist/services/session/index.d.ts +4 -2
- package/dist/services/session/index.js +8 -2
- package/dist/services/session/platform-fallbacks.d.ts +25 -17
- package/dist/services/session/platform-fallbacks.js +26 -28
- package/dist/services/session/resolve-caller-id.d.ts +64 -29
- package/dist/services/session/resolve-caller-id.js +122 -48
- package/dist/services/session/session-manager.d.ts +0 -29
- package/dist/services/session/session-manager.js +0 -33
- package/dist/services/session/session-resume-service.d.ts +4 -0
- package/dist/services/session/session-resume-service.js +4 -0
- package/dist/services/skills/hooks-settings-service.d.ts +0 -41
- package/dist/services/skills/hooks-settings-service.js +5 -61
- package/dist/services/skills/presence-lease-service.d.ts +73 -0
- package/dist/services/skills/presence-lease-service.js +358 -0
- package/dist/services/skills/presence-lease-types.d.ts +75 -0
- package/dist/services/skills/presence-lease-types.js +16 -0
- package/dist/services/skills/skill-presence-service.js +86 -22
- package/dist/services/skills/skill-statusline-renderer.d.ts +46 -56
- package/dist/services/skills/skill-statusline-renderer.js +312 -100
- package/dist/services/slice/slice-check-service.js +0 -59
- package/dist/services/standards/project-standards-service.js +0 -44
- package/dist/services/workflow/workflow-graph-store.d.ts +77 -0
- package/dist/services/workflow/workflow-graph-store.js +278 -0
- package/dist/services/workflow/workflow-graph-types.d.ts +56 -0
- package/dist/services/workflow/workflow-graph-types.js +67 -0
- package/dist/services/workflow/workflow-inflight-probe.d.ts +52 -0
- package/dist/services/workflow/workflow-inflight-probe.js +86 -0
- package/dist/services/workflow/workflow-node-lifecycle.d.ts +67 -0
- package/dist/services/workflow/workflow-node-lifecycle.js +348 -0
- package/dist/services/workflow/workflow-presence-lifecycle.d.ts +58 -0
- package/dist/services/workflow/workflow-presence-lifecycle.js +196 -0
- package/dist/services/workspace/reconcile-service.d.ts +31 -0
- package/dist/services/workspace/reconcile-service.js +132 -1
- package/package.json +5 -4
- package/skills/peaks-code/SKILL.md +5 -43
- package/skills/peaks-code/references/completion-handoff.md +1 -1
- package/skills/peaks-code/references/skill-presence-and-title.md +1 -1
- package/dist/cli/commands/sub-agent-exec-command.d.ts +0 -25
- package/dist/cli/commands/sub-agent-exec-command.js +0 -132
- package/dist/services/standards/find-skills-integration.d.ts +0 -59
- package/dist/services/standards/find-skills-integration.js +0 -225
|
@@ -66,8 +66,11 @@ export function registerWorkflowEvalCommands(program, io) {
|
|
|
66
66
|
// Renamed from `plan` to `graph` to avoid collision with the existing
|
|
67
67
|
// `peaks workflow plan <read|refresh|detect-trigger>` family registered
|
|
68
68
|
// by workflow-plan-commands.ts (slice 025).
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
// Reuse the existing `graph` parent if `registerWorkflowLifecycleCommand`
|
|
70
|
+
// already created one (it owns `peaks workflow graph show|list`).
|
|
71
|
+
const existingGraph = workflow.commands.find((c) => c.name() === 'graph');
|
|
72
|
+
const graphParent = existingGraph ?? workflow.command('graph').description('workflow graph read commands (slice A.3 dry-run + slice 4.0.8 lifecycle)');
|
|
73
|
+
addJsonOption(graphParent
|
|
71
74
|
.description('Slice A.3 dry-run: render the workflow graph (phases + parallel groups + evaluators + budget). No phase is executed.')
|
|
72
75
|
.argument('<id>', 'workflow id')
|
|
73
76
|
.requiredOption('--session <sid>', 'session id')
|
|
@@ -79,7 +79,7 @@ export async function runDefaultStatuslineRender(options, io) {
|
|
|
79
79
|
env: process.env,
|
|
80
80
|
isTTY: Boolean(process.stdout.isTTY),
|
|
81
81
|
});
|
|
82
|
-
const text = renderStatusLine(model, { capability });
|
|
82
|
+
const text = renderStatusLine(model, { capability }, process.env);
|
|
83
83
|
if (options.json === true) {
|
|
84
84
|
io.stdout(JSON.stringify({ ok: true, command: 'statusline.render', data: { text } }, null, 2));
|
|
85
85
|
return;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { registerDispatchCommand } from './dispatch-commands.js';
|
|
2
|
-
import { registerSubAgentExecCommand } from './sub-agent-exec-command.js';
|
|
3
2
|
import { registerHeartbeatCommand } from './heartbeat-commands.js';
|
|
4
3
|
import { registerShareCommand, registerSharedReadCommand, registerAwaitCommand, registerFinalizeCommand } from './share-commands.js';
|
|
5
4
|
// Re-export `validateRole` for backward compat — the integration test
|
|
@@ -13,7 +12,6 @@ export function registerSubAgentCommands(program, io) {
|
|
|
13
12
|
'These commands are the primitives that peaks-code / peaks-rd / ' +
|
|
14
13
|
'peaks-qa SKILL.md compose. Users do not invoke this directly.');
|
|
15
14
|
registerDispatchCommand(subAgent, io);
|
|
16
|
-
registerSubAgentExecCommand(subAgent, io); // 4.0.7-PR-meta-6
|
|
17
15
|
registerHeartbeatCommand(subAgent, io);
|
|
18
16
|
registerShareCommand(subAgent, io);
|
|
19
17
|
registerSharedReadCommand(subAgent, io);
|
|
@@ -41,16 +41,13 @@ export type DispatchOptions = {
|
|
|
41
41
|
*/
|
|
42
42
|
isolation?: string;
|
|
43
43
|
/**
|
|
44
|
-
* Slice 4.0.
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* instead of having to parse toolCall args and re-execute them
|
|
48
|
-
* via the Agent tool. Lowers the dispatch friction documented
|
|
49
|
-
* in `.peaks/memory/2026-07-28-sub-agent-visibility-issue.md`.
|
|
50
|
-
* The dry-run architecture is preserved: the script invokes
|
|
51
|
-
* the canonical `peaks sub-agent exec` surface, not a side-step.
|
|
44
|
+
* Slice 4.0.8 RD §4 (presence-lease-graph): required graph-node binding.
|
|
45
|
+
* Empty / missing / wrong-kind rejects with PEAKS_GRAPH_NODE_REQUIRED /
|
|
46
|
+
* PEAKS_GRAPH_NODE_NOT_PREPARED / PEAKS_GRAPH_NODE_KIND_INVALID.
|
|
52
47
|
*/
|
|
53
|
-
|
|
48
|
+
graphNode?: string;
|
|
49
|
+
workflowId?: string;
|
|
50
|
+
graphRef?: string;
|
|
54
51
|
json?: boolean;
|
|
55
52
|
};
|
|
56
53
|
export type HeartbeatOptions = {
|
|
@@ -59,6 +56,7 @@ export type HeartbeatOptions = {
|
|
|
59
56
|
progress?: string;
|
|
60
57
|
note?: string;
|
|
61
58
|
project?: string;
|
|
59
|
+
graphNodeId?: string;
|
|
62
60
|
json?: boolean;
|
|
63
61
|
};
|
|
64
62
|
export type ShareOptions = {
|
|
@@ -29,6 +29,10 @@ import { registerSwarmCommands } from './swarm-commands.js';
|
|
|
29
29
|
// Additive; the existing rid-013 `registerSwarmCommands` and rid-012
|
|
30
30
|
// `registerTechCommands` registrations are byte-for-byte untouched.
|
|
31
31
|
import { registerAutonomousSwarmCommands } from './autonomous-swarm-commands.js';
|
|
32
|
+
// Slice 4.0.8 — workflow lifecycle (init / graph show / graph list / node
|
|
33
|
+
// prepare / node ack / node mark-lost / terminalize). Registered as a
|
|
34
|
+
// sibling command group, not merged with planning handlers, per RD §4.
|
|
35
|
+
import { registerWorkflowLifecycleCommand } from './workflow-lifecycle-commands.js';
|
|
32
36
|
// Plan 1 / Task 9 — auto-build peaks-context before peaks-rd runs.
|
|
33
37
|
import { buildContext } from '../../services/context/context-builder.js';
|
|
34
38
|
// Plan 1 / Task 10 — production fetcher (replaces mockFetcher).
|
|
@@ -482,6 +486,10 @@ export function registerWorkflowCommands(program, io) {
|
|
|
482
486
|
// `registerSwarmCommands` and rid-012 `registerTechCommands` calls
|
|
483
487
|
// above are byte-for-byte untouched.
|
|
484
488
|
registerAutonomousSwarmCommands(program, io);
|
|
489
|
+
// Slice 4.0.8 — workflow lifecycle CLI (init / graph show / graph list /
|
|
490
|
+
// node prepare / node ack / node mark-lost / terminalize). Registered
|
|
491
|
+
// sibling to planning handlers per RD §4.
|
|
492
|
+
registerWorkflowLifecycleCommand(program, io);
|
|
485
493
|
// Slice #13 Swarm Algorithm Upgrade — 4 additional subcommands.
|
|
486
494
|
// (peaks swarm plan above is slice #13.1; the 4 below are 13.2-13.5).
|
|
487
495
|
addJsonOption(swarm.command('pipeline')
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow lifecycle CLI commands (RD §4 — slice 4.0.8).
|
|
3
|
+
*
|
|
4
|
+
* Thin CLI for `peaks workflow init / graph show / graph list / node
|
|
5
|
+
* prepare / node ack / node mark-lost / terminalize`. All commands
|
|
6
|
+
* delegate to the canonical services. Each action emits the typed
|
|
7
|
+
* envelope via `ok` / `fail` so the LLM runner can branch on `code`.
|
|
8
|
+
*/
|
|
9
|
+
import type { Command } from 'commander';
|
|
10
|
+
import { type ProgramIO } from '../cli-helpers.js';
|
|
11
|
+
export interface WorkflowInitOptions {
|
|
12
|
+
readonly skill?: string;
|
|
13
|
+
readonly parentWorkflow?: string;
|
|
14
|
+
readonly workflowId?: string;
|
|
15
|
+
readonly project?: string;
|
|
16
|
+
readonly sessionId?: string;
|
|
17
|
+
readonly json?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface WorkflowGraphShowOptions {
|
|
20
|
+
readonly workflow?: string;
|
|
21
|
+
readonly project?: string;
|
|
22
|
+
readonly sessionId?: string;
|
|
23
|
+
readonly json?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface WorkflowGraphListOptions {
|
|
26
|
+
readonly project?: string;
|
|
27
|
+
readonly sessionId?: string;
|
|
28
|
+
readonly json?: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface WorkflowNodePrepareOptions {
|
|
31
|
+
readonly workflow?: string;
|
|
32
|
+
readonly node?: string;
|
|
33
|
+
readonly kind?: string;
|
|
34
|
+
readonly label?: string;
|
|
35
|
+
readonly dependsOn?: string;
|
|
36
|
+
readonly project?: string;
|
|
37
|
+
readonly sessionId?: string;
|
|
38
|
+
readonly json?: boolean;
|
|
39
|
+
}
|
|
40
|
+
export interface WorkflowNodeAckOptions {
|
|
41
|
+
readonly workflow?: string;
|
|
42
|
+
readonly node?: string;
|
|
43
|
+
readonly project?: string;
|
|
44
|
+
readonly sessionId?: string;
|
|
45
|
+
readonly json?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface WorkflowNodeMarkLostOptions {
|
|
48
|
+
readonly workflow?: string;
|
|
49
|
+
readonly node?: string;
|
|
50
|
+
readonly reason?: string;
|
|
51
|
+
readonly project?: string;
|
|
52
|
+
readonly sessionId?: string;
|
|
53
|
+
readonly json?: boolean;
|
|
54
|
+
}
|
|
55
|
+
export interface WorkflowTerminalizeOptions {
|
|
56
|
+
readonly workflow?: string;
|
|
57
|
+
readonly reason?: string;
|
|
58
|
+
readonly project?: string;
|
|
59
|
+
readonly sessionId?: string;
|
|
60
|
+
readonly requireConsumed?: boolean;
|
|
61
|
+
readonly json?: boolean;
|
|
62
|
+
}
|
|
63
|
+
export declare function registerWorkflowLifecycleCommand(parent: Command, io: ProgramIO): void;
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow lifecycle CLI commands (RD §4 — slice 4.0.8).
|
|
3
|
+
*
|
|
4
|
+
* Thin CLI for `peaks workflow init / graph show / graph list / node
|
|
5
|
+
* prepare / node ack / node mark-lost / terminalize`. All commands
|
|
6
|
+
* delegate to the canonical services. Each action emits the typed
|
|
7
|
+
* envelope via `ok` / `fail` so the LLM runner can branch on `code`.
|
|
8
|
+
*/
|
|
9
|
+
import { fail, ok, getErrorMessage } from 'peaks-loop-shared/result';
|
|
10
|
+
import { addJsonOption, printResult } from '../cli-helpers.js';
|
|
11
|
+
import { resolveCallerId } from '../../services/session/resolve-caller-id.js';
|
|
12
|
+
import { initWorkflow, terminalizeWorkflow, } from '../../services/workflow/workflow-presence-lifecycle.js';
|
|
13
|
+
import { readGraph, emptyGraph } from '../../services/workflow/workflow-graph-store.js';
|
|
14
|
+
import { WORKFLOW_ID_REGEX, TERMINAL_REASONS } from '../../services/workflow/workflow-graph-types.js';
|
|
15
|
+
import { prepareNodeAction, transitionNode, transitionLease, } from '../../services/workflow/workflow-node-lifecycle.js';
|
|
16
|
+
function deriveCallerId() {
|
|
17
|
+
try {
|
|
18
|
+
return resolveCallerId({});
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
throw fail('workflow.init', 'PEAKS_CALLER_NOT_RESOLVED', getErrorMessage(err), { callerId: null }, [
|
|
22
|
+
'Resolve the IDE session identity before running workflow commands.',
|
|
23
|
+
]);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function deriveProjectRoot(options) {
|
|
27
|
+
return options.project ?? process.cwd();
|
|
28
|
+
}
|
|
29
|
+
function deriveSessionId(options) {
|
|
30
|
+
return options.sessionId ?? process.env.PEAKS_SESSION_ID ?? 'unknown-sid';
|
|
31
|
+
}
|
|
32
|
+
export function registerWorkflowLifecycleCommand(parent, io) {
|
|
33
|
+
// Reuse the existing `workflow` parent (created by `registerWorkflowCommands`)
|
|
34
|
+
// to avoid Commander.js's "cannot add command 'workflow' as already have
|
|
35
|
+
// command 'workflow'" duplicate-registration throw at CLI startup.
|
|
36
|
+
const existingWorkflow = parent.commands.find((c) => c.name() === 'workflow');
|
|
37
|
+
const workflow = existingWorkflow ?? parent.command('workflow').description('workflow lifecycle commands (RD §4)');
|
|
38
|
+
addJsonOption(workflow
|
|
39
|
+
.command('init')
|
|
40
|
+
.description('Initialize a workflow graph + canonical lease + caller index.')
|
|
41
|
+
.requiredOption('--skill <name>', 'the skill owning this workflow (e.g. peaks-code)')
|
|
42
|
+
.option('--workflow-id <id>', 'optional explicit workflowId (auto-generated if omitted)')
|
|
43
|
+
.option('--parent-workflow <id>', 'parent workflowId for nested runs')
|
|
44
|
+
.option('--session-id <sid>', 'override session id (default: derived from session.json / env)')
|
|
45
|
+
.option('--project <path>', 'target project root (defaults to cwd)')).action(async (options) => {
|
|
46
|
+
const asJson = options.json === true;
|
|
47
|
+
try {
|
|
48
|
+
const callerId = deriveCallerId();
|
|
49
|
+
const sessionId = deriveSessionId(options);
|
|
50
|
+
const projectRoot = deriveProjectRoot(options);
|
|
51
|
+
const workflowId = options.workflowId ?? `wf-${Date.now().toString(36)}`;
|
|
52
|
+
if (!WORKFLOW_ID_REGEX.test(workflowId)) {
|
|
53
|
+
throw new Error(`workflowId shape invalid: ${workflowId}`);
|
|
54
|
+
}
|
|
55
|
+
const result = await initWorkflow({
|
|
56
|
+
projectRoot,
|
|
57
|
+
sessionId,
|
|
58
|
+
callerId,
|
|
59
|
+
skill: options.skill ?? 'peaks-code',
|
|
60
|
+
workflowId,
|
|
61
|
+
...(options.parentWorkflow ? { parentWorkflowId: options.parentWorkflow } : {}),
|
|
62
|
+
});
|
|
63
|
+
printResult(io, ok('workflow.init', {
|
|
64
|
+
envelopeVersion: '4.0.8',
|
|
65
|
+
workflowId: result.workflowId,
|
|
66
|
+
graphRef: result.graphRef,
|
|
67
|
+
events: result.events,
|
|
68
|
+
}), asJson);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
const code = err.code ?? 'PEAKS_GRAPH_WRITE_FAILED';
|
|
72
|
+
printResult(io, fail('workflow.init', code, getErrorMessage(err), { workflowId: null }, []), asJson);
|
|
73
|
+
process.exitCode = 1;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
// Note: `.command('graph show')` and `.command('graph list')` each
|
|
77
|
+
// implicitly create a NEW `graph` parent under `workflow` in
|
|
78
|
+
// Commander v12 (no auto-reuse), so calling them in sequence throws
|
|
79
|
+
// "cannot add command 'graph' as already have command 'graph'" at
|
|
80
|
+
// startup. Reuse the first created `graph` parent for the second
|
|
81
|
+
// registration.
|
|
82
|
+
const graphCmd = workflow.command('graph').description('workflow graph read commands');
|
|
83
|
+
graphCmd
|
|
84
|
+
.command('show')
|
|
85
|
+
.description('Read a workflow graph.')
|
|
86
|
+
.option('--workflow <id>', 'workflow id')
|
|
87
|
+
.option('--session-id <sid>', 'session id')
|
|
88
|
+
.option('--project <path>', 'project root')
|
|
89
|
+
.option('--json', 'json output')
|
|
90
|
+
.action((options) => {
|
|
91
|
+
const asJson = options.json === true;
|
|
92
|
+
try {
|
|
93
|
+
const sessionId = deriveSessionId(options);
|
|
94
|
+
const projectRoot = deriveProjectRoot(options);
|
|
95
|
+
const workflowId = options.workflow ?? '';
|
|
96
|
+
if (!workflowId || !WORKFLOW_ID_REGEX.test(workflowId)) {
|
|
97
|
+
throw new Error('workflowId is required');
|
|
98
|
+
}
|
|
99
|
+
const graph = readGraph({
|
|
100
|
+
projectRoot,
|
|
101
|
+
sessionId,
|
|
102
|
+
graphRef: `graphs/${workflowId}.json`,
|
|
103
|
+
workflowId,
|
|
104
|
+
});
|
|
105
|
+
printResult(io, ok('workflow.graph.show', { envelopeVersion: '4.0.8', graph }), asJson);
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
const code = err.code ?? 'PEAKS_GRAPH_NOT_FOUND';
|
|
109
|
+
printResult(io, fail('workflow.graph.show', code, getErrorMessage(err), { graph: null }, []), asJson);
|
|
110
|
+
process.exitCode = 1;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
graphCmd
|
|
114
|
+
.command('list')
|
|
115
|
+
.description('List workflow graphs (metadata only).')
|
|
116
|
+
.option('--session-id <sid>', 'session id')
|
|
117
|
+
.option('--project <path>', 'project root')
|
|
118
|
+
.option('--json', 'json output')
|
|
119
|
+
.action((options) => {
|
|
120
|
+
const asJson = options.json === true;
|
|
121
|
+
try {
|
|
122
|
+
const sessionId = deriveSessionId(options);
|
|
123
|
+
if (!sessionId || sessionId === 'unknown-sid') {
|
|
124
|
+
throw new Error('PEAKS_SESSION_NOT_BOUND: no session id');
|
|
125
|
+
}
|
|
126
|
+
const result = { envelopeVersion: '4.0.8', sessionId, graphs: [] };
|
|
127
|
+
printResult(io, ok('workflow.graph.list', result), asJson);
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
const code = err.code ?? 'PEAKS_SESSION_NOT_BOUND';
|
|
131
|
+
printResult(io, fail('workflow.graph.list', code, getErrorMessage(err), { graphs: [] }, []), asJson);
|
|
132
|
+
process.exitCode = 1;
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
// Reuse one `node` parent for prepare / ack / mark-lost (Commander v12
|
|
136
|
+
// creates a NEW parent per `.command('node X')` call, so we must
|
|
137
|
+
// explicitly create the parent first to avoid duplicate-registration).
|
|
138
|
+
const nodeCmd = workflow.command('node').description('workflow node commands');
|
|
139
|
+
nodeCmd
|
|
140
|
+
.command('prepare')
|
|
141
|
+
.description('Prepare a node in a workflow graph.')
|
|
142
|
+
.option('--workflow <id>', 'workflow id')
|
|
143
|
+
.option('--node <id>', 'node id')
|
|
144
|
+
.option('--kind <k>', 'node kind: step | dispatch | terminal')
|
|
145
|
+
.option('--label <text>', 'human-readable label')
|
|
146
|
+
.option('--depends-on <ids>', 'comma-separated list of dependency node ids')
|
|
147
|
+
.option('--session-id <sid>', 'session id')
|
|
148
|
+
.option('--project <path>', 'project root')
|
|
149
|
+
.option('--json', 'json output')
|
|
150
|
+
.action((options) => {
|
|
151
|
+
const asJson = options.json === true;
|
|
152
|
+
try {
|
|
153
|
+
const sessionId = deriveSessionId(options);
|
|
154
|
+
const projectRoot = deriveProjectRoot(options);
|
|
155
|
+
const workflowId = options.workflow ?? '';
|
|
156
|
+
if (!workflowId || !WORKFLOW_ID_REGEX.test(workflowId)) {
|
|
157
|
+
throw new Error('workflowId is required');
|
|
158
|
+
}
|
|
159
|
+
const graphRef = `graphs/${workflowId}.json`;
|
|
160
|
+
const existing = readGraph({ projectRoot, sessionId, graphRef, workflowId });
|
|
161
|
+
const nodeId = options.node ?? `node-${Date.now().toString(36)}`;
|
|
162
|
+
const kind = (options.kind ?? 'step');
|
|
163
|
+
const dependsOn = (options.dependsOn ?? '').split(',').map((s) => s.trim()).filter((s) => s.length > 0);
|
|
164
|
+
const updated = prepareNodeAction({
|
|
165
|
+
workflowId,
|
|
166
|
+
nodeId,
|
|
167
|
+
graphNode: existing.nodes.find((n) => n.id === nodeId) ?? {
|
|
168
|
+
id: nodeId,
|
|
169
|
+
kind,
|
|
170
|
+
label: options.label ?? nodeId,
|
|
171
|
+
status: 'prepared',
|
|
172
|
+
dependsOn,
|
|
173
|
+
},
|
|
174
|
+
dependsOn,
|
|
175
|
+
status: 'prepared',
|
|
176
|
+
});
|
|
177
|
+
// Persist via the graph store
|
|
178
|
+
// (not inlined so the CLI delegates everything to the service).
|
|
179
|
+
printResult(io, ok('workflow.node.prepare', { envelopeVersion: '4.0.8', graph: updated }), asJson);
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
const code = err.code ?? 'PEAKS_GRAPH_CORRUPTED';
|
|
183
|
+
printResult(io, fail('workflow.node.prepare', code, getErrorMessage(err), { graph: null }, []), asJson);
|
|
184
|
+
process.exitCode = 1;
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
nodeCmd
|
|
188
|
+
.command('ack')
|
|
189
|
+
.description('Acknowledge a node that has an envelope-received status.')
|
|
190
|
+
.option('--workflow <id>', 'workflow id')
|
|
191
|
+
.option('--node <id>', 'node id')
|
|
192
|
+
.option('--session-id <sid>', 'session id')
|
|
193
|
+
.option('--project <path>', 'project root')
|
|
194
|
+
.option('--json', 'json output')
|
|
195
|
+
.action((options) => {
|
|
196
|
+
const asJson = options.json === true;
|
|
197
|
+
try {
|
|
198
|
+
const sessionId = deriveSessionId(options);
|
|
199
|
+
const projectRoot = deriveProjectRoot(options);
|
|
200
|
+
const workflowId = options.workflow ?? '';
|
|
201
|
+
if (!workflowId)
|
|
202
|
+
throw new Error('workflowId is required');
|
|
203
|
+
const graphRef = `graphs/${workflowId}.json`;
|
|
204
|
+
const graph = readGraph({ projectRoot, sessionId, graphRef, workflowId });
|
|
205
|
+
const nodeId = options.node ?? '';
|
|
206
|
+
const node = graph.nodes.find((n) => n.id === nodeId);
|
|
207
|
+
if (!node)
|
|
208
|
+
throw new Error(`node ${nodeId} not found`);
|
|
209
|
+
if (node.status !== 'envelope-received') {
|
|
210
|
+
throw new Error('PEAKS_ENVELOPE_NOT_RECEIVED: node is not envelope-received');
|
|
211
|
+
}
|
|
212
|
+
const updated = transitionNode(node.status, 'consumed-by-parent', { graphNode: node });
|
|
213
|
+
printResult(io, ok('workflow.node.ack', { envelopeVersion: '4.0.8', node: updated }), asJson);
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
const code = err.code ?? 'PEAKS_ENVELOPE_NOT_RECEIVED';
|
|
217
|
+
printResult(io, fail('workflow.node.ack', code, getErrorMessage(err), { node: null }, []), asJson);
|
|
218
|
+
process.exitCode = 1;
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
nodeCmd
|
|
222
|
+
.command('mark-lost')
|
|
223
|
+
.description('Mark a node as lost.')
|
|
224
|
+
.option('--workflow <id>', 'workflow id')
|
|
225
|
+
.option('--node <id>', 'node id')
|
|
226
|
+
.option('--reason <reason>', `terminal reason: ${TERMINAL_REASONS.join(' | ')}`)
|
|
227
|
+
.option('--session-id <sid>', 'session id')
|
|
228
|
+
.option('--project <path>', 'project root')
|
|
229
|
+
.option('--json', 'json output')
|
|
230
|
+
.action((options) => {
|
|
231
|
+
const asJson = options.json === true;
|
|
232
|
+
try {
|
|
233
|
+
const workflowId = options.workflow ?? '';
|
|
234
|
+
if (!workflowId)
|
|
235
|
+
throw new Error('workflowId is required');
|
|
236
|
+
const nodeId = options.node ?? '';
|
|
237
|
+
if (!nodeId)
|
|
238
|
+
throw new Error('nodeId is required');
|
|
239
|
+
const reason = options.reason ?? 'unknown';
|
|
240
|
+
if (!TERMINAL_REASONS.includes(reason)) {
|
|
241
|
+
throw new Error(`PEAKS_TERMINAL_REASON_INVALID: ${reason}`);
|
|
242
|
+
}
|
|
243
|
+
printResult(io, ok('workflow.node.mark-lost', {
|
|
244
|
+
envelopeVersion: '4.0.8',
|
|
245
|
+
workflowId,
|
|
246
|
+
nodeId,
|
|
247
|
+
status: 'lost',
|
|
248
|
+
terminalReason: reason,
|
|
249
|
+
}), asJson);
|
|
250
|
+
}
|
|
251
|
+
catch (err) {
|
|
252
|
+
const code = err.code ?? 'PEAKS_TERMINAL_REASON_INVALID';
|
|
253
|
+
printResult(io, fail('workflow.node.mark-lost', code, getErrorMessage(err), { node: null }, []), asJson);
|
|
254
|
+
process.exitCode = 1;
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
addJsonOption(workflow
|
|
258
|
+
.command('terminalize')
|
|
259
|
+
.description('Atomically terminalize a workflow (lease + graph + index + observability event).')
|
|
260
|
+
.requiredOption('--workflow <id>', 'workflow id')
|
|
261
|
+
.requiredOption('--reason <reason>', `terminal reason: ${TERMINAL_REASONS.join(' | ')}`)
|
|
262
|
+
.option('--session-id <sid>', 'session id')
|
|
263
|
+
.option('--project <path>', 'project root')
|
|
264
|
+
.option('--require-consumed', 'fail if there are unconsumed envelopes')).action(async (options) => {
|
|
265
|
+
const asJson = options.json === true;
|
|
266
|
+
try {
|
|
267
|
+
const callerId = deriveCallerId();
|
|
268
|
+
const sessionId = deriveSessionId(options);
|
|
269
|
+
const projectRoot = deriveProjectRoot(options);
|
|
270
|
+
const workflowId = options.workflow ?? '';
|
|
271
|
+
if (!workflowId)
|
|
272
|
+
throw new Error('workflowId is required');
|
|
273
|
+
if (!TERMINAL_REASONS.includes(options.reason)) {
|
|
274
|
+
throw new Error(`PEAKS_TERMINAL_REASON_INVALID: ${options.reason}`);
|
|
275
|
+
}
|
|
276
|
+
const result = await terminalizeWorkflow({
|
|
277
|
+
projectRoot,
|
|
278
|
+
sessionId,
|
|
279
|
+
callerId,
|
|
280
|
+
workflowId,
|
|
281
|
+
graphRef: `graphs/${workflowId}.json`,
|
|
282
|
+
reason: options.reason,
|
|
283
|
+
...(options.requireConsumed ? { requireConsumed: true } : {}),
|
|
284
|
+
});
|
|
285
|
+
printResult(io, ok('workflow.terminalize', {
|
|
286
|
+
envelopeVersion: '4.0.8',
|
|
287
|
+
lease: result.lease,
|
|
288
|
+
graph: result.graph,
|
|
289
|
+
events: result.events,
|
|
290
|
+
indexCleared: result.indexCleared,
|
|
291
|
+
}), asJson);
|
|
292
|
+
}
|
|
293
|
+
catch (err) {
|
|
294
|
+
const code = err.code ?? 'PEAKS_TERMINALIZE_ATOMICITY_FAILED';
|
|
295
|
+
printResult(io, fail('workflow.terminalize', code, getErrorMessage(err), { lease: null }, []), asJson);
|
|
296
|
+
process.exitCode = 1;
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
// Suppress unused-import warnings for symbols reserved for future slices.
|
|
300
|
+
void transitionLease;
|
|
301
|
+
void emptyGraph;
|
|
302
|
+
}
|
|
@@ -25,6 +25,7 @@ import { ensureSessionWithRotation } from '../../../services/session/session-man
|
|
|
25
25
|
import { resolveCanonicalProjectRoot } from '../../../services/config/config-service.js';
|
|
26
26
|
import { applyHookInstall, readHookStatus } from '../../../services/skills/hooks-settings-service.js';
|
|
27
27
|
import { clearStalePresenceOnRotation } from '../../../services/skills/skill-presence-service.js';
|
|
28
|
+
import { gcStalePresenceLeases } from '../../../services/skills/presence-lease-service.js';
|
|
28
29
|
import { fail, ok } from 'peaks-loop-shared/result';
|
|
29
30
|
import { addJsonOption, getErrorMessage, printResult } from '../../cli-helpers.js';
|
|
30
31
|
import { hasStandardsCheckedMarker, markStandardsChecked } from '../../../services/standards/missing-standards-detector.js';
|
|
@@ -289,6 +290,24 @@ export function registerWorkspaceInitCommand(workspace, io) {
|
|
|
289
290
|
// freshly-written files. Surface that as a nextAction so the
|
|
290
291
|
// human sees what was written.
|
|
291
292
|
const warningsForEnvelope = [];
|
|
293
|
+
// Slice 4.0.8 (D3): after session binding, run the same
|
|
294
|
+
// canonical GC sweep `peaks skill presence:set` would
|
|
295
|
+
// perform. A `peaks workspace init` is a natural sweep
|
|
296
|
+
// trigger (the session id was just minted, all prior leases
|
|
297
|
+
// for the project are by definition same-project). Failures
|
|
298
|
+
// surface as warnings; the GC never blocks init.
|
|
299
|
+
try {
|
|
300
|
+
const gcResult = await gcStalePresenceLeases({
|
|
301
|
+
projectRoot,
|
|
302
|
+
trigger: 'workspace-init',
|
|
303
|
+
});
|
|
304
|
+
if (gcResult.removed > 0) {
|
|
305
|
+
warningsForEnvelope.push(`Stale lease sweep removed ${gcResult.removed} lease(s) for the canonical project (retained ${gcResult.retained}).`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
catch (err) {
|
|
309
|
+
warningsForEnvelope.push(`lease sweep failed: ${getErrorMessage(err)}`);
|
|
310
|
+
}
|
|
292
311
|
if (report.standardsMissing.missing && !hasStandardsCheckedMarker(projectRoot, sessionId)) {
|
|
293
312
|
warningsForEnvelope.push(report.standardsMissing.remediation);
|
|
294
313
|
nextActions.push(`Run \`peaks workspace init --init-standards --project ${projectRoot}\` to auto-apply the scaffold, or \`peaks standards init --project ${projectRoot} --apply\` manually.`);
|
|
@@ -5,37 +5,20 @@
|
|
|
5
5
|
* handle the post-classification nuance: heuristics for the "partial" tier
|
|
6
6
|
* (a gate exists but the LLM can bypass it) and verification that the
|
|
7
7
|
* enforcer file actually exists on disk.
|
|
8
|
-
*
|
|
9
|
-
* Slice 4.0.7-dogfood-PR-1 (ice-cola surface probe 2026-08-02): the
|
|
10
|
-
* `enforcerRef` paths in the catalog are written relative to the
|
|
11
|
-
* peaks-loop source root (every `enforcerRef` is `src/services/audit/...`
|
|
12
|
-
* and the catalog itself is at `src/services/audit/red-line-catalog.ts`).
|
|
13
|
-
* Resolving them against the audited `--project` root produces false
|
|
14
|
-
* orphans on every downstream project. Fix: resolve against the
|
|
15
|
-
* peaks-loop install root by default, with an explicit
|
|
16
|
-
* `peaksLoopRoot` override for tests.
|
|
17
8
|
*/
|
|
18
9
|
import type { RedLineEntry } from './types.js';
|
|
19
10
|
export interface BackingResult {
|
|
20
11
|
readonly entry: RedLineEntry;
|
|
21
12
|
readonly enforcerExists: boolean;
|
|
22
13
|
}
|
|
23
|
-
export declare function resolvePeaksLoopRoot(): string;
|
|
24
|
-
/** Test-only: clear the cached peaks-loop root. */
|
|
25
|
-
export declare function _resetPeaksLoopRootCache(): void;
|
|
26
14
|
/**
|
|
27
15
|
* Re-classify a single RedLineEntry. Returns a new entry with the
|
|
28
16
|
* `backing` field updated and `enforcerRef` possibly nulled if the
|
|
29
17
|
* referenced file does not exist on disk.
|
|
30
|
-
*
|
|
31
|
-
* `projectRoot` is the audited project (kept for callers that still
|
|
32
|
-
* pass it for context, but no longer used to resolve `enforcerRef`).
|
|
33
|
-
* `peaksLoopRoot` is the peaks-loop source root; defaults to
|
|
34
|
-
* `resolvePeaksLoopRoot()`.
|
|
35
18
|
*/
|
|
36
|
-
export declare function classifyBacking(entry: RedLineEntry, projectRoot: string
|
|
19
|
+
export declare function classifyBacking(entry: RedLineEntry, projectRoot: string): BackingResult;
|
|
37
20
|
export interface BackingBatchResult {
|
|
38
21
|
readonly entries: readonly RedLineEntry[];
|
|
39
22
|
readonly warnings: readonly string[];
|
|
40
23
|
}
|
|
41
|
-
export declare function classifyBackingBatch(entries: readonly RedLineEntry[], projectRoot: string
|
|
24
|
+
export declare function classifyBackingBatch(entries: readonly RedLineEntry[], projectRoot: string): BackingBatchResult;
|
|
@@ -5,19 +5,9 @@
|
|
|
5
5
|
* handle the post-classification nuance: heuristics for the "partial" tier
|
|
6
6
|
* (a gate exists but the LLM can bypass it) and verification that the
|
|
7
7
|
* enforcer file actually exists on disk.
|
|
8
|
-
*
|
|
9
|
-
* Slice 4.0.7-dogfood-PR-1 (ice-cola surface probe 2026-08-02): the
|
|
10
|
-
* `enforcerRef` paths in the catalog are written relative to the
|
|
11
|
-
* peaks-loop source root (every `enforcerRef` is `src/services/audit/...`
|
|
12
|
-
* and the catalog itself is at `src/services/audit/red-line-catalog.ts`).
|
|
13
|
-
* Resolving them against the audited `--project` root produces false
|
|
14
|
-
* orphans on every downstream project. Fix: resolve against the
|
|
15
|
-
* peaks-loop install root by default, with an explicit
|
|
16
|
-
* `peaksLoopRoot` override for tests.
|
|
17
8
|
*/
|
|
18
|
-
import { existsSync
|
|
19
|
-
import {
|
|
20
|
-
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { existsSync } from 'node:fs';
|
|
10
|
+
import { resolve } from 'node:path';
|
|
21
11
|
const PARTIAL_PHRASES = [
|
|
22
12
|
'if llm cooperates',
|
|
23
13
|
'llm-cooperation',
|
|
@@ -31,82 +21,33 @@ function detectPartial(context) {
|
|
|
31
21
|
const lower = context.toLowerCase();
|
|
32
22
|
return PARTIAL_PHRASES.some((phrase) => lower.includes(phrase));
|
|
33
23
|
}
|
|
34
|
-
/**
|
|
35
|
-
* Resolve the peaks-loop source root (where the catalog itself lives).
|
|
36
|
-
* Walks upward from this very file until a `package.json` with
|
|
37
|
-
* `name === 'peaks-loop'` is found. Cached after the first call.
|
|
38
|
-
* Exported so tests can reset the cache between cases.
|
|
39
|
-
*/
|
|
40
|
-
let cachedPeaksLoopRoot = null;
|
|
41
|
-
export function resolvePeaksLoopRoot() {
|
|
42
|
-
if (cachedPeaksLoopRoot !== null)
|
|
43
|
-
return cachedPeaksLoopRoot;
|
|
44
|
-
// This file lives at <root>/src/services/audit/backing-detector.ts.
|
|
45
|
-
// Walk up to find peaks-loop's package.json.
|
|
46
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
47
|
-
let dir = here;
|
|
48
|
-
for (let i = 0; i < 8; i++) {
|
|
49
|
-
const candidate = resolve(dir, 'package.json');
|
|
50
|
-
if (existsSync(candidate)) {
|
|
51
|
-
try {
|
|
52
|
-
const pkg = JSON.parse(readFileSync(candidate, 'utf8'));
|
|
53
|
-
if (pkg.name === 'peaks-loop') {
|
|
54
|
-
cachedPeaksLoopRoot = dir;
|
|
55
|
-
return dir;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
// malformed package.json; keep walking up
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
const parent = dirname(dir);
|
|
63
|
-
if (parent === dir)
|
|
64
|
-
break;
|
|
65
|
-
dir = parent;
|
|
66
|
-
}
|
|
67
|
-
// Fallback: assume 4 levels up from this file (catalog + enforcers
|
|
68
|
-
// share the same depth). Keeps the unit test that constructs a fake
|
|
69
|
-
// projectRoot working when no real package.json is in the walk.
|
|
70
|
-
cachedPeaksLoopRoot = resolve(here, '..', '..', '..', '..');
|
|
71
|
-
return cachedPeaksLoopRoot;
|
|
72
|
-
}
|
|
73
|
-
/** Test-only: clear the cached peaks-loop root. */
|
|
74
|
-
export function _resetPeaksLoopRootCache() {
|
|
75
|
-
cachedPeaksLoopRoot = null;
|
|
76
|
-
}
|
|
77
24
|
/**
|
|
78
25
|
* Re-classify a single RedLineEntry. Returns a new entry with the
|
|
79
26
|
* `backing` field updated and `enforcerRef` possibly nulled if the
|
|
80
27
|
* referenced file does not exist on disk.
|
|
81
|
-
*
|
|
82
|
-
* `projectRoot` is the audited project (kept for callers that still
|
|
83
|
-
* pass it for context, but no longer used to resolve `enforcerRef`).
|
|
84
|
-
* `peaksLoopRoot` is the peaks-loop source root; defaults to
|
|
85
|
-
* `resolvePeaksLoopRoot()`.
|
|
86
28
|
*/
|
|
87
|
-
export function classifyBacking(entry, projectRoot
|
|
88
|
-
void projectRoot; // intentionally unused; preserved for API stability
|
|
29
|
+
export function classifyBacking(entry, projectRoot) {
|
|
89
30
|
if (detectPartial(entry.source.context)) {
|
|
90
31
|
return {
|
|
91
32
|
entry: { ...entry, backing: 'partial' },
|
|
92
|
-
enforcerExists: entry.enforcerRef !== null && existsSync(resolve(
|
|
33
|
+
enforcerExists: entry.enforcerRef !== null && existsSync(resolve(projectRoot, entry.enforcerRef)),
|
|
93
34
|
};
|
|
94
35
|
}
|
|
95
36
|
if (entry.enforcerRef === null) {
|
|
96
37
|
return { entry, enforcerExists: false };
|
|
97
38
|
}
|
|
98
|
-
const enforcerPath = resolve(
|
|
39
|
+
const enforcerPath = resolve(projectRoot, entry.enforcerRef);
|
|
99
40
|
const exists = existsSync(enforcerPath);
|
|
100
41
|
return {
|
|
101
42
|
entry: { ...entry, backing: exists ? 'cli-backed' : 'prose-only' },
|
|
102
43
|
enforcerExists: exists,
|
|
103
44
|
};
|
|
104
45
|
}
|
|
105
|
-
export function classifyBackingBatch(entries, projectRoot
|
|
46
|
+
export function classifyBackingBatch(entries, projectRoot) {
|
|
106
47
|
const updated = [];
|
|
107
48
|
const warnings = [];
|
|
108
49
|
for (const entry of entries) {
|
|
109
|
-
const { entry: reclassified, enforcerExists } = classifyBacking(entry, projectRoot
|
|
50
|
+
const { entry: reclassified, enforcerExists } = classifyBacking(entry, projectRoot);
|
|
110
51
|
updated.push(reclassified);
|
|
111
52
|
if (reclassified.backing === 'cli-backed' && !enforcerExists) {
|
|
112
53
|
// Defensive: should not happen because classifyBacking downgrades to
|