peaks-loop 4.0.8 → 4.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/cli/commands/_register.js +2 -0
  3. package/dist/cli/commands/baseline-commands.d.ts +3 -0
  4. package/dist/cli/commands/baseline-commands.js +146 -0
  5. package/dist/cli/commands/container-commands.js +2 -1
  6. package/dist/cli/commands/core/skill-command.js +32 -5
  7. package/dist/cli/commands/openspec-commands.js +2 -1
  8. package/dist/cli/commands/statusline-commands.js +1 -1
  9. package/dist/services/audit/enforcers/active-skill-resolver.d.ts +11 -0
  10. package/dist/services/audit/enforcers/active-skill-resolver.js +50 -39
  11. package/dist/services/capability-audit-service/cross-check.d.ts +8 -0
  12. package/dist/services/capability-audit-service/cross-check.js +11 -0
  13. package/dist/services/capability-audit-service/index.d.ts +3 -0
  14. package/dist/services/capability-audit-service/index.js +2 -0
  15. package/dist/services/capability-audit-service/runner.d.ts +27 -0
  16. package/dist/services/capability-audit-service/runner.js +40 -0
  17. package/dist/services/capability-audit-service/staleness.d.ts +1 -0
  18. package/dist/services/capability-audit-service/staleness.js +7 -0
  19. package/dist/services/capability-audit-service/types.d.ts +24 -0
  20. package/dist/services/capability-audit-service/types.js +1 -0
  21. package/dist/services/capability-baseline/store.d.ts +33 -0
  22. package/dist/services/capability-baseline/store.js +80 -0
  23. package/dist/services/capability-baseline/types.d.ts +45 -0
  24. package/dist/services/capability-baseline/types.js +5 -0
  25. package/dist/services/capability-baseline/validator.d.ts +19 -0
  26. package/dist/services/capability-baseline/validator.js +38 -0
  27. package/dist/services/capability-guard-runner/contracts/J01.d.ts +2 -0
  28. package/dist/services/capability-guard-runner/contracts/J01.js +48 -0
  29. package/dist/services/capability-guard-runner/contracts/J02.d.ts +2 -0
  30. package/dist/services/capability-guard-runner/contracts/J02.js +34 -0
  31. package/dist/services/capability-guard-runner/contracts/J03.d.ts +2 -0
  32. package/dist/services/capability-guard-runner/contracts/J03.js +24 -0
  33. package/dist/services/capability-guard-runner/contracts/J04.d.ts +2 -0
  34. package/dist/services/capability-guard-runner/contracts/J04.js +40 -0
  35. package/dist/services/capability-guard-runner/contracts/J05.d.ts +2 -0
  36. package/dist/services/capability-guard-runner/contracts/J05.js +21 -0
  37. package/dist/services/capability-guard-runner/contracts/J06.d.ts +2 -0
  38. package/dist/services/capability-guard-runner/contracts/J06.js +43 -0
  39. package/dist/services/capability-guard-runner/contracts/J07.d.ts +2 -0
  40. package/dist/services/capability-guard-runner/contracts/J07.js +53 -0
  41. package/dist/services/capability-guard-runner/contracts/J08.d.ts +2 -0
  42. package/dist/services/capability-guard-runner/contracts/J08.js +43 -0
  43. package/dist/services/capability-guard-runner/contracts/J09.d.ts +2 -0
  44. package/dist/services/capability-guard-runner/contracts/J09.js +44 -0
  45. package/dist/services/capability-guard-runner/contracts/J10.d.ts +2 -0
  46. package/dist/services/capability-guard-runner/contracts/J10.js +40 -0
  47. package/dist/services/capability-guard-runner/contracts/J11.d.ts +2 -0
  48. package/dist/services/capability-guard-runner/contracts/J11.js +39 -0
  49. package/dist/services/capability-guard-runner/contracts/J12.d.ts +2 -0
  50. package/dist/services/capability-guard-runner/contracts/J12.js +34 -0
  51. package/dist/services/capability-guard-runner/contracts/J13.d.ts +2 -0
  52. package/dist/services/capability-guard-runner/contracts/J13.js +45 -0
  53. package/dist/services/capability-guard-runner/contracts/J14.d.ts +2 -0
  54. package/dist/services/capability-guard-runner/contracts/J14.js +36 -0
  55. package/dist/services/capability-guard-runner/contracts/J15.d.ts +2 -0
  56. package/dist/services/capability-guard-runner/contracts/J15.js +40 -0
  57. package/dist/services/capability-guard-runner/diff.d.ts +2 -0
  58. package/dist/services/capability-guard-runner/diff.js +7 -0
  59. package/dist/services/capability-guard-runner/runner.d.ts +9 -0
  60. package/dist/services/capability-guard-runner/runner.js +22 -0
  61. package/dist/services/capability-guard-runner/types.d.ts +39 -0
  62. package/dist/services/capability-guard-runner/types.js +1 -0
  63. package/dist/services/container/container-lease.js +2 -1
  64. package/dist/services/evolution/evolution-types.d.ts +16 -16
  65. package/dist/services/final-review/final-review-service.d.ts +8 -0
  66. package/dist/services/final-review/final-review-service.js +14 -0
  67. package/dist/services/final-review/final-review-types.d.ts +1 -1
  68. package/dist/services/impact/impact-scan-service.js +4 -3
  69. package/dist/services/job/job-progress-store.d.ts +2 -2
  70. package/dist/services/job/job-types.d.ts +26 -26
  71. package/dist/services/migrate-skill-name/migrate.js +2 -1
  72. package/dist/services/observability/observability-service.d.ts +2 -2
  73. package/dist/services/openspec/artifact-boundary.js +3 -2
  74. package/dist/services/openspec/coverage-evidence-reader.js +9 -8
  75. package/dist/services/prd/handoff-auto-regen.js +2 -1
  76. package/dist/services/scan/type-sanity-service.js +2 -1
  77. package/dist/services/sediment/json-schema.d.ts +4 -4
  78. package/dist/services/session/session-binding-bridge.js +17 -19
  79. package/dist/services/session/session-manager.js +36 -12
  80. package/dist/services/session/session-resume-service.d.ts +4 -0
  81. package/dist/services/session/session-resume-service.js +4 -0
  82. package/dist/services/skills/skill-statusline-renderer.d.ts +46 -56
  83. package/dist/services/skills/skill-statusline-renderer.js +335 -126
  84. package/dist/services/skills/skill-statusline-service.d.ts +6 -0
  85. package/dist/services/skills/skill-statusline-service.js +105 -7
  86. package/dist/services/vm/vm-lease.js +2 -1
  87. package/dist/services/workflow/workflow-autonomous-resume-helpers.js +3 -2
  88. package/dist/services/workspace/workspace-service.js +2 -1
  89. package/dist/services/worktree/worktree-lease.js +2 -1
  90. package/dist/shared/path-safety.js +3 -5
  91. package/dist/shared/path-utils.d.ts +48 -0
  92. package/dist/shared/path-utils.js +65 -1
  93. package/package.json +5 -4
@@ -9,6 +9,7 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, unlinkS
9
9
  import { mkdir as mkdirAsync } from 'node:fs/promises';
10
10
  import { dirname, join, resolve } from 'node:path';
11
11
  import { randomBytes } from 'node:crypto';
12
+ import { projectRootsMatch, stableRealPath } from '../../shared/path-utils.js';
12
13
  import { ensureSession } from './session-binding-bridge.js';
13
14
  // As of slice 2026-06-05-peaks-runtime-layer the project-level session
14
15
  // binding lives under `.peaks/_runtime/session.json`. The legacy
@@ -89,17 +90,22 @@ function getSessionFilePath(projectRoot) {
89
90
  * Read existing session info from disk.
90
91
  * Returns null if no session file exists or if it's invalid.
91
92
  *
92
- * Strict equality on `data.projectRoot === projectRoot` is
93
- * preserved here on purpose: many other modules depend on
94
- * the strict-equality semantics to test the "no session bound"
95
- * code path. Changing the read semantics here would cascade
96
- * into many test failures — out of scope for the progress
97
- * rebind fix.
93
+ * The `projectRoot` comparison is canonicalized (separator, symlink,
94
+ * and — on Windows only — case) via `projectRootsMatch`. Before
95
+ * slice `2026-08-04-rid-001-path-canonicalize` this was a strict
96
+ * `===`, which returned null whenever the stored form differed
97
+ * cosmetically from the caller-passed form. On Windows Git Bash that
98
+ * is the common case: `peaks workspace init` stores
99
+ * `C:\Users\...\peaks-loop` while the caller passes
100
+ * `C:/Users/.../peaks-loop`, so every `getSessionId` returned null and
101
+ * `presence:set` failed closed with `PEAKS_SESSION_NOT_BOUND` —
102
+ * surfacing to the user as a permanent `peaks empty` statusline.
98
103
  *
99
- * The progress subcommands (which are the surface that
100
- * actually breaks on the rebind bug) use
101
- * `getSessionIdCanonical` instead, which does the
102
- * canonicalize-on-read resolution the bug fix needs.
104
+ * Canonicalization is a strict widening: paths that denote the same
105
+ * physical directory now match, and paths that denote different
106
+ * directories still do not (pinned by the Case 4 regression test in
107
+ * `tests/unit/session/session-manager-path-canonicalize.test.ts`), so
108
+ * the "no session bound" code path other modules depend on is intact.
103
109
  */
104
110
  function readSessionFile(projectRoot) {
105
111
  const sessionFile = getSessionFilePath(projectRoot);
@@ -112,7 +118,7 @@ function readSessionFile(projectRoot) {
112
118
  return null;
113
119
  try {
114
120
  const data = JSON.parse(readFileSync(pathToRead, 'utf8'));
115
- if (data.sessionId && data.projectRoot === projectRoot) {
121
+ if (data.sessionId && typeof data.projectRoot === 'string' && projectRootsMatch(data.projectRoot, projectRoot)) {
116
122
  return data;
117
123
  }
118
124
  return null;
@@ -157,6 +163,13 @@ function readSessionFileCanonical(projectRoot) {
157
163
  * `.peaks/_runtime/session.json`. The `.peaks/_runtime/` directory is
158
164
  * created on demand. The legacy `.peaks/.session.json` is NOT written by
159
165
  * this slice; it is only read for back-compat.
166
+ *
167
+ * The persisted `projectRoot` is passed through `stableRealPath` so the
168
+ * stored form is symlink-resolved and stable across callers. We store
169
+ * the REAL path, not the `projectRootCompareKey` — the key is
170
+ * lossy (lower-cased on Windows) and is only ever a comparison
171
+ * artifact. Reads tolerate either form via `projectRootsMatch`, so
172
+ * bindings written by older versions keep resolving.
160
173
  */
161
174
  function writeSessionFile(projectRoot, info) {
162
175
  const sessionFile = getSessionFilePath(projectRoot);
@@ -164,7 +177,18 @@ function writeSessionFile(projectRoot, info) {
164
177
  if (!existsSync(dir)) {
165
178
  mkdirSync(dir, { recursive: true });
166
179
  }
167
- writeFileSync(sessionFile, JSON.stringify(info, null, 2), 'utf8');
180
+ let canonicalProjectRoot;
181
+ try {
182
+ canonicalProjectRoot = stableRealPath(info.projectRoot);
183
+ }
184
+ catch {
185
+ // Never block a write on canonicalization: if the path cannot be
186
+ // realpath'd, persist the caller's form unchanged. Reads canonicalize
187
+ // both sides anyway, so a non-canonical stored value still matches.
188
+ canonicalProjectRoot = info.projectRoot;
189
+ }
190
+ const canonicalInfo = { ...info, projectRoot: canonicalProjectRoot };
191
+ writeFileSync(sessionFile, JSON.stringify(canonicalInfo, null, 2), 'utf8');
168
192
  }
169
193
  /**
170
194
  * Drop the project-level session binding at the canonical
@@ -6,6 +6,10 @@
6
6
  * prepend to its own prompt. The output is LLM-friendly: stable
7
7
  * headings, monospace paths, and bullet lists — easy for the model to
8
8
  * scan and reason about.
9
+ *
10
+ * Resume locates the deepestGate (the latest completed gate in the
11
+ * workflow) so the skill can pick up the next step without re-asking
12
+ * the user for state already captured at that gate.
9
13
  */
10
14
  import { type CheckpointSnapshot } from './session-checkpoint-service.js';
11
15
  export interface ResumeOptions {
@@ -6,6 +6,10 @@
6
6
  * prepend to its own prompt. The output is LLM-friendly: stable
7
7
  * headings, monospace paths, and bullet lists — easy for the model to
8
8
  * scan and reason about.
9
+ *
10
+ * Resume locates the deepestGate (the latest completed gate in the
11
+ * workflow) so the skill can pick up the next step without re-asking
12
+ * the user for state already captured at that gate.
9
13
  */
10
14
  import { existsSync, readFileSync } from 'node:fs';
11
15
  import { basename, sep } from 'node:path';
@@ -14,66 +14,44 @@ export type StatusLineCapability = 'ansi-unicode' | 'unicode' | 'ascii';
14
14
  export interface StatusLineRenderOptions {
15
15
  readonly capability: StatusLineCapability;
16
16
  }
17
- /**
18
- * Resolve the capability tier from environment and TTY state. Pure and
19
- * deterministic — no I/O beyond reading the caller-supplied `env` and
20
- * `isTTY` flag. The CLI delegates to this so the read-only status line
21
- * does not need to know about ANSI/NO_COLOR semantics.
22
- *
23
- * The optional `forced` argument is a TEST SEAM (consumed by direct unit
24
- * tests, not by the CLI). It lets tests pin a tier without constructing a
25
- * TTY/NO_COLOR fixture. The CLI does NOT expose any flag that maps to
26
- * `forced` — the env-driven path is the single first-version runtime
27
- * source of truth.
28
- *
29
- * Adapter-internal env override (NOT a user-facing CLI flag):
30
- * `PEAKS_STATUSLINE_ASCII` — when set to `1` / `true` / `yes`, the
31
- * renderer drops to the ASCII palette (no Unicode-extra glyphs, no ANSI).
32
- * This is an adapter-internal mechanism for the trust boundary described
33
- * by the two-forms-only / human-NL-choice-only tenets: the user never
34
- * types a CLI verb to flip palettes, but the adapter (and only the
35
- * adapter) can set the env var on the user's behalf when its consumer
36
- * (e.g. a tiny terminal without UTF-8) needs ASCII. We do NOT expose
37
- * this via a CLI flag.
38
- *
39
- * Order of resolution (highest priority first):
40
- *
41
- * 1. `forced` argument (test seam only)
42
- * 2. `PEAKS_STATUSLINE_ASCII` set → `ascii` (adapter-internal override)
43
- * 3. `isTTY === true` → `ansi-unicode`
44
- * 4. otherwise → `unicode` (still ANSI-colored; only `ascii` is color-free)
45
- *
46
- * Why this ordering: the IDE statusline invokes the renderer through a
47
- * hook that may not always be detected as a TTY. The `unicode` tier
48
- * now emits the cyan brand accent + semantic colors so the line reads
49
- * as a branded product surface whether or not the consumer is a
50
- * terminal. `NO_COLOR` is honored as an ambient convention by the
51
- * IDE adapter (which can set `PEAKS_STATUSLINE_ASCII=1` to opt out
52
- * fully), so a separate tier for NO_COLOR is no longer needed.
53
- *
54
- * `ascii` is the strict no-color, no-Unicode-extra tier; it is the
55
- * single safe option for plain log / file consumers.
56
- *
57
- * Why `PEAKS_STATUSLINE_ASCII` outranks `NO_COLOR`:
58
- * The two env vars are NOT redundant. `NO_COLOR` (https://no-color.org)
59
- * is the cross-industry signal that NO escape sequences should be emitted
60
- * — its only effect is "ANSI off". The Unicode-extra glyphs (●, █, ░, etc.)
61
- * are NOT ANSI escape sequences; they are UTF-8 characters and `NO_COLOR`
62
- * does not address them. `PEAKS_STATUSLINE_ASCII` is the adapter-internal
63
- * "signal source is a tiny terminal without UTF-8" override and drops the
64
- * glyphs to ASCII shape as well. Both are correct signals about
65
- * different concerns; the ASCII override wins because it is strictly
66
- * narrower (a tiny terminal needs both no-ANSI AND no-Unicode-extra).
67
- *
68
- * The deliberate ordering keeps the rendered text ANSI-free when the consumer
69
- * is a logger, a file, or any non-interactive sink, and only enables ANSI
70
- * under an explicit supported condition (TTY + no env veto).
71
- */
72
17
  export declare function resolveStatusLineCapability(input: {
73
18
  readonly env: NodeJS.ProcessEnv;
74
19
  readonly isTTY: boolean;
75
20
  readonly forced?: StatusLineCapability;
76
21
  }): StatusLineCapability;
22
+ export declare function isNoColorEnv(env: NodeJS.ProcessEnv): boolean;
23
+ /**
24
+ * Visible-character width of an ANSI-bearing string. Skips every
25
+ * `\x1b[...m` escape so the count reflects what the terminal paints,
26
+ * not the byte length on the wire.
27
+ */
28
+ export declare function visibleCharWidth(s: string): number;
29
+ interface AnsiToken {
30
+ readonly kind: 'text' | 'esc';
31
+ readonly value: string;
32
+ }
33
+ /**
34
+ * Split an ANSI string into a flat token stream. Escape sequences are
35
+ * grouped as `\x1b[...m` (single token, kind='esc'); everything else
36
+ * is grouped as a single 'text' run. Used by {@link applyMarquee} to
37
+ * locate visible-cell ranges inside an SGR-bearing string.
38
+ */
39
+ export declare function tokenizeAnsi(s: string): readonly AnsiToken[];
40
+ /**
41
+ * Apply the marquee highlight band to an ANSI-bearing string. Pure.
42
+ * - `bandStart` / `bandEnd` are visible-cell indices (0-based).
43
+ * - Cells in [bandStart, bandEnd] receive a `\x1b[1;38;2;224;224;224m`
44
+ * prefix; cells after the band receive the closing `\x1b[0m` reset
45
+ * so the rest of the line keeps its original SGR.
46
+ * - Empty strings and zero-width bands return the input unchanged.
47
+ */
48
+ export declare function applyMarqueeHighlight(s: string, bandStart: number, bandEnd: number): string;
49
+ /**
50
+ * Compute the marquee band for a string at time `nowMs` and re-emit
51
+ * the string with the band applied. ASCII tier is a pass-through (no
52
+ * SGR to inject) — the scanner has no visible effect on plain text.
53
+ */
54
+ export declare function applyMarquee(s: string, nowMs: number, capability: StatusLineCapability): string;
77
55
  /**
78
56
  * Render the status line. Pure — no I/O, no side effects. The output is
79
57
  * a single short line suitable for the bottom-of-terminal status bar.
@@ -85,5 +63,17 @@ export declare function resolveStatusLineCapability(input: {
85
63
  * Compact-state precedence: when `model.compact.kind !== 'none'` the
86
64
  * compact bar replaces the active/idle/stale content. The C1 baseline
87
65
  * line is preserved when the compact state is `none`.
66
+ *
67
+ * The full line is wrapped by {@link applyMarquee} so the brand-purple
68
+ * text + non-focal bar cells share a single colour surface while the
69
+ * scanner band paints its `#E0E0E0` highlight over a moving slice.
70
+ *
71
+ * Idle suppression: the marquee is OFF when `model.state === 'idle'`
72
+ * (and no compact state is active). Idle keeps the slow-blink `○`
73
+ * as the only motion — the user is asking whether the harness is
74
+ * running a skill; the scan band would compete for attention and
75
+ * obscure that question. Active / stale / invalid-presence / compact
76
+ * states all keep the marquee.
88
77
  */
89
- export declare function renderStatusLine(model: StatusLineModel, options?: StatusLineRenderOptions): string;
78
+ export declare function renderStatusLine(model: StatusLineModel, options?: StatusLineRenderOptions, env?: NodeJS.ProcessEnv): string;
79
+ export {};