peaks-loop 4.0.46 → 4.0.48
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 +54 -0
- package/README-en.md +1 -1
- package/README.md +1 -1
- package/agents/karpathy-reviewer.md +11 -10
- package/dist/cli/cli-helpers.d.ts +34 -0
- package/dist/cli/cli-helpers.js +57 -0
- package/dist/cli/commands/code-job-shape-commands.js +8 -0
- package/dist/cli/commands/code-runtime-commands.d.ts +22 -0
- package/dist/cli/commands/code-runtime-commands.js +139 -16
- package/dist/cli/commands/compact-command.js +241 -1
- package/dist/cli/commands/config-commands.js +15 -9
- package/dist/cli/commands/container-commands.js +3 -3
- package/dist/cli/commands/core/skill-command.js +45 -10
- package/dist/cli/commands/cron-commands.js +2 -1
- package/dist/cli/commands/dashboard-long-run.js +6 -0
- package/dist/cli/commands/dispatch-commands.js +11 -1
- package/dist/cli/commands/doctor/invoke-from-code.js +6 -0
- package/dist/cli/commands/e2e-verify.js +3 -3
- package/dist/cli/commands/governance-classify-contract-commands.js +1 -0
- package/dist/cli/commands/hooks-commands.js +14 -5
- package/dist/cli/commands/job-commands.js +8 -0
- package/dist/cli/commands/loop-commands.js +1 -0
- package/dist/cli/commands/loop-eval-commands.js +15 -0
- package/dist/cli/commands/perf-audit-commands.js +2 -0
- package/dist/cli/commands/playwright-commands.js +14 -1
- package/dist/cli/commands/prd-commands.js +1 -1
- package/dist/cli/commands/qa-commands.js +22 -0
- package/dist/cli/commands/reinject-command.d.ts +72 -0
- package/dist/cli/commands/reinject-command.js +174 -0
- package/dist/cli/commands/request-commands.js +14 -3
- package/dist/cli/commands/scan-commands.js +1 -1
- package/dist/cli/commands/security-audit-commands.js +2 -0
- package/dist/cli/commands/shadcn-commands.js +1 -0
- package/dist/cli/commands/slice-integrate-commands.js +5 -0
- package/dist/cli/commands/statusline-commands.js +44 -4
- package/dist/cli/commands/sub-agent/detached.d.ts +14 -1
- package/dist/cli/commands/sub-agent/detached.js +47 -22
- package/dist/cli/commands/sub-agent-shutdown-commands.js +11 -0
- package/dist/cli/commands/test-commands.js +2 -1
- package/dist/cli/commands/verdict-aggregate-command.js +95 -13
- package/dist/cli/commands/vm-commands.js +7 -7
- package/dist/cli/commands/workflow-commands.js +1 -1
- package/dist/cli/commands/workspace/init-command.js +24 -2
- package/dist/cli/commands/worktree-lease-commands.js +4 -4
- package/dist/cli/index.js +10 -3
- package/dist/cli/program.js +5 -0
- package/dist/hooks/pre-tool-use-sub-agent.js +1 -1
- package/dist/services/adapter/adapter-registry.js +1 -1
- package/dist/services/artifacts/artifact-prerequisites.d.ts +38 -7
- package/dist/services/artifacts/artifact-prerequisites.js +130 -65
- package/dist/services/artifacts/artifact-service.js +1 -1
- package/dist/services/artifacts/request-artifact-service.d.ts +8 -0
- package/dist/services/artifacts/request-artifact-service.js +18 -8
- package/dist/services/artifacts/request-artifact-state-helpers.d.ts +57 -0
- package/dist/services/artifacts/request-artifact-state-helpers.js +91 -10
- package/dist/services/audit-independent/perf-audit-service.d.ts +9 -0
- package/dist/services/audit-independent/perf-audit-service.js +27 -5
- package/dist/services/audit-independent/security-audit-service.d.ts +12 -2
- package/dist/services/audit-independent/security-audit-service.js +28 -6
- package/dist/services/capability-guard-runner/contracts/J01.js +2 -1
- package/dist/services/capability-guard-runner/contracts/J02.js +3 -3
- package/dist/services/capability-guard-runner/contracts/J04.js +4 -2
- package/dist/services/capability-guard-runner/contracts/J07.js +2 -1
- package/dist/services/code/auto-compact-lifecycle.d.ts +130 -1
- package/dist/services/code/auto-compact-lifecycle.js +180 -4
- package/dist/services/code/auto-compact-orchestrator.d.ts +53 -9
- package/dist/services/code/auto-compact-orchestrator.js +166 -34
- package/dist/services/code/compact-event-settle.d.ts +122 -0
- package/dist/services/code/compact-event-settle.js +219 -0
- package/dist/services/code/orchestrator-can-do.d.ts +4 -2
- package/dist/services/code/orchestrator-can-do.js +37 -5
- package/dist/services/codegraph/codegraph-exclude-reconciler.js +2 -1
- package/dist/services/codegraph/codegraph-process-runner.js +3 -2
- package/dist/services/compact/request-transition-hook.js +5 -2
- package/dist/services/compact-history/compact-history-service.d.ts +75 -0
- package/dist/services/compact-history/compact-history-service.js +49 -0
- package/dist/services/config/config-restore.d.ts +12 -1
- package/dist/services/config/config-restore.js +35 -4
- package/dist/services/config/config-rollback.js +6 -1
- package/dist/services/config/config-safety.d.ts +52 -0
- package/dist/services/config/config-safety.js +75 -1
- package/dist/services/context/auto-compact-dispatcher.d.ts +7 -37
- package/dist/services/context/auto-compact-dispatcher.js +113 -40
- package/dist/services/context/auto-compact-reader.d.ts +68 -28
- package/dist/services/context/auto-compact-reader.js +155 -1
- package/dist/services/context/auto-compact-types.d.ts +89 -12
- package/dist/services/context/auto-compact-types.js +16 -32
- package/dist/services/context/harness-context-witness.d.ts +310 -0
- package/dist/services/context/harness-context-witness.js +606 -0
- package/dist/services/context/harness-window-config.d.ts +412 -0
- package/dist/services/context/harness-window-config.js +607 -0
- package/dist/services/context/main-session-monitor.d.ts +27 -0
- package/dist/services/context/main-session-monitor.js +32 -1
- package/dist/services/context/post-compact-reinjection.d.ts +221 -0
- package/dist/services/context/post-compact-reinjection.js +491 -0
- package/dist/services/dispatch/merge-back-runner.js +5 -5
- package/dist/services/dispatch/service-shutdown.js +3 -3
- package/dist/services/doc/doc-generator.js +2 -1
- package/dist/services/env/shell-probe.js +1 -1
- package/dist/services/evidence/evidence-generator.js +86 -49
- package/dist/services/final-review/final-review-service.d.ts +9 -0
- package/dist/services/final-review/final-review-service.js +36 -12
- package/dist/services/fuzzy-matching/fzf-pick-service.js +2 -0
- package/dist/services/hooks/auto-compact-hook-install.d.ts +10 -2
- package/dist/services/hooks/auto-compact-hook-install.js +8 -0
- package/dist/services/ide/adapters/claude-code-adapter.d.ts +107 -3
- package/dist/services/ide/adapters/claude-code-adapter.js +154 -7
- package/dist/services/ide/ide-registry.d.ts +31 -0
- package/dist/services/ide/ide-registry.js +35 -0
- package/dist/services/ide/ide-types.d.ts +59 -0
- package/dist/services/job/job-state-store.js +7 -0
- package/dist/services/lint/detect-eslint.js +2 -2
- package/dist/services/lint/eslint-runner.js +3 -1
- package/dist/services/loop/evaluator-dispatcher.js +2 -1
- package/dist/services/memory/project-memory-service/index/kind-dispatch.js +1 -1
- package/dist/services/memory/project-memory-service/store/paths.d.ts +9 -1
- package/dist/services/memory/project-memory-service/store/paths.js +15 -6
- package/dist/services/polyrepo/polyrepo-dispatcher.js +11 -0
- package/dist/services/prd/best-practice-auto-trigger.js +1 -0
- package/dist/services/prd/handoff-auto-regen.js +31 -27
- package/dist/services/prd/handoff-frontmatter.d.ts +44 -0
- package/dist/services/prd/handoff-frontmatter.js +75 -0
- package/dist/services/prd/handoff-service.d.ts +41 -2
- package/dist/services/prd/handoff-service.js +81 -8
- package/dist/services/prd/handoff-types.d.ts +3 -2
- package/dist/services/prd/handoff-types.js +3 -2
- package/dist/services/qa/qa-business-review-state.js +9 -0
- package/dist/services/release/version-precheck-service.d.ts +2 -1
- package/dist/services/release/version-precheck-service.js +82 -12
- package/dist/services/runtime/vendor-adapter.d.ts +29 -4
- package/dist/services/runtime/vendors/claude-code.js +1 -1
- package/dist/services/runtime/vendors/codex.js +1 -1
- package/dist/services/runtime/vendors/copilot.js +1 -1
- package/dist/services/sc/sc-service.js +1 -1
- package/dist/services/scan/diff-scope-service.js +2 -2
- package/dist/services/scan/file-size-scan.js +2 -2
- package/dist/services/scan/karpathy-service.js +2 -2
- package/dist/services/scan/orphan-service.js +2 -1
- package/dist/services/scan/type-sanity-service.js +2 -2
- package/dist/services/session/session-checkpoint-service.js +8 -0
- package/dist/services/skill/resume-detector.js +29 -11
- package/dist/services/skillhub/tar-runtime.js +1 -0
- package/dist/services/skills/hooks-codegate-superpowers.d.ts +14 -0
- package/dist/services/skills/hooks-codegate-superpowers.js +99 -3
- package/dist/services/skills/hooks-settings-service.d.ts +12 -0
- package/dist/services/skills/hooks-settings-service.js +91 -14
- package/dist/services/skills/session-start-hook-constants.d.ts +86 -0
- package/dist/services/skills/session-start-hook-constants.js +86 -0
- package/dist/services/skills/skill-presence-service.js +9 -0
- package/dist/services/skills/skill-statusline-service.d.ts +14 -0
- package/dist/services/slice/slice-check-service.js +31 -12
- package/dist/services/slice/slice-decompose-runners.js +2 -1
- package/dist/services/slice/slice-review-state.js +8 -0
- package/dist/services/upgrade/upgrade-service.js +1 -0
- package/dist/services/workflow/pipeline-verify-gate-support.d.ts +47 -10
- package/dist/services/workflow/pipeline-verify-gate-support.js +212 -93
- package/dist/services/workflow/pipeline-verify-service.js +24 -23
- package/dist/services/workflow/pipeline-verify-types.d.ts +10 -3
- package/dist/services/workflow/workflow-skip-service.js +2 -1
- package/dist/services/workspace/claude-settings-template.d.ts +56 -8
- package/dist/services/workspace/claude-settings-template.js +98 -20
- package/dist/services/workspace/migrate-service.js +1 -1
- package/dist/services/workspace/workspace-claude-settings-materializer.js +124 -9
- package/dist/services/workspace/workspace-service.js +8 -0
- package/dist/services/worktree/host-worktree-reconciler.js +1 -0
- package/dist/services/worktree/long-path-cleanup.js +3 -2
- package/dist/shared/process.js +1 -1
- package/package.json +6 -6
- package/scripts/install-skills.mjs +1 -0
- package/scripts/watch.mjs +3 -1
- package/skills/bee/peaks-perf-audit/SKILL.md +1 -1
- package/skills/bee/peaks-prd/SKILL.md +8 -6
- package/skills/bee/peaks-qa/SKILL.md +7 -7
- package/skills/bee/peaks-qa/references/qa-runbook.md +2 -2
- package/skills/bee/peaks-qa/references/qa-transition-gates.md +7 -7
- package/skills/bee/peaks-rd/SKILL.md +10 -8
- package/skills/bee/peaks-rd/references/artifact-per-request.md +2 -2
- package/skills/bee/peaks-rd/references/parallel-review-fanout.md +7 -5
- package/skills/bee/peaks-rd/references/rd-fanout-contracts.md +13 -13
- package/skills/bee/peaks-rd/references/rd-runbook.md +9 -5
- package/skills/bee/peaks-rd/references/rd-transition-gates.md +9 -7
- package/skills/bee/peaks-rd/references/writing-handoff-frontmatter.md +6 -6
- package/skills/bee/peaks-reviewer/SKILL.md +1 -1
- package/skills/bee/peaks-sc/SKILL.md +1 -1
- package/skills/bee/peaks-security-audit/SKILL.md +1 -1
- package/skills/bee/peaks-txt/SKILL.md +1 -1
- package/skills/bee/peaks-ui/SKILL.md +1 -1
- package/skills/peaks-audit/SKILL.md +1 -1
- package/skills/peaks-code/SKILL.md +3 -3
- package/skills/peaks-code/references/a2a-artifact-mapping.md +3 -3
- package/skills/peaks-code/references/local-artifact-workspace.md +1 -1
- package/skills/peaks-code/references/resume-detection.md +13 -7
- package/skills/peaks-code/references/runbook.md +3 -2
- package/skills/peaks-code/references/session-overload-signal-index.md +2 -1
- package/skills/peaks-code/references/sub-agent-dispatch.md +1 -1
- package/skills/peaks-code/references/workflow-gates-and-types.md +8 -6
- package/skills/peaks-content/SKILL.md +1 -1
- package/skills/peaks-doctor/SKILL.md +1 -1
- package/skills/peaks-final-review/SKILL.md +1 -1
- package/skills/peaks-ide/SKILL.md +1 -1
- package/skills/peaks-issue-fix-orchestrator/SKILL.md +1 -1
- package/skills/peaks-resume/SKILL.md +1 -1
- package/skills/peaks-slice-decompose/SKILL.md +1 -1
- package/skills/peaks-solo/SKILL.md +1 -1
- package/skills/peaks-sop/SKILL.md +1 -1
- package/skills/peaks-status/SKILL.md +1 -1
- package/skills/peaks-test/SKILL.md +1 -1
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-compact engineering-state re-injection
|
|
3
|
+
* (rid `2026-09-13-a2-post-compact-reinject`, session
|
|
4
|
+
* `2026-09-12-session-e37ef0`).
|
|
5
|
+
*
|
|
6
|
+
* THE PROBLEM THIS SOLVES
|
|
7
|
+
*
|
|
8
|
+
* `94bacb4a` settled WHO decides when to compact: peaks-loop owns the window,
|
|
9
|
+
* the harness performs the compaction. This module owns what happens AFTER
|
|
10
|
+
* it. Compaction summarizes a conversation, and a summarizer has no notion of
|
|
11
|
+
* a slice, a request, a gate or an acceptance criterion — so the engineering
|
|
12
|
+
* state is exactly the part it is most likely to drop. The user's word for
|
|
13
|
+
* the symptom is 飘逸 (drift): the session keeps talking but has lost its
|
|
14
|
+
* place.
|
|
15
|
+
*
|
|
16
|
+
* This repository already has the case on file:
|
|
17
|
+
* `.peaks/memory/per-turn-obligations-belong-in-per-turn-output.md` —
|
|
18
|
+
* text that lives in the message history (a SKILL.md body, a dispatch prompt)
|
|
19
|
+
* does not survive a compaction, so a rule that only exists there stops
|
|
20
|
+
* applying. The fix has to be re-injection, not a better summary.
|
|
21
|
+
*
|
|
22
|
+
* WHY A BUDGET IS THE DESIGN AND NOT A DETAIL
|
|
23
|
+
*
|
|
24
|
+
* The harness's own supported mechanism is a `SessionStart` hook whose stdout
|
|
25
|
+
* it adds to the context. So the card competes for the very space the
|
|
26
|
+
* compaction just freed. Re-injecting a lot is WORSE than re-injecting
|
|
27
|
+
* nothing: it re-fills the window, the ratio climbs back, and the next
|
|
28
|
+
* compaction arrives sooner — the cost is paid on every cycle. Hence a hard,
|
|
29
|
+
* declared ceiling (`POST_COMPACT_REINJECTION_BYTE_BUDGET`) and a
|
|
30
|
+
* deterministic rule for what is dropped (see `renderReinjectionCard`).
|
|
31
|
+
*
|
|
32
|
+
* POINT, DO NOT INLINE
|
|
33
|
+
*
|
|
34
|
+
* Two different things can be put in the card:
|
|
35
|
+
* - a FACT whose absence changes behaviour on the very next turn (the
|
|
36
|
+
* runtime rules, the next slice, the identity of the session), and
|
|
37
|
+
* - a BODY of material the model can go and read (a request artifact, a
|
|
38
|
+
* job state file, a memory file).
|
|
39
|
+
* Inlining the second is the worse trade at every size: it costs bytes on
|
|
40
|
+
* every compaction cycle forever, it goes stale the moment it is written, and
|
|
41
|
+
* the model cannot tell a stale copy from a live one. A path costs ~60 bytes,
|
|
42
|
+
* is always current, and the model is going to read the file anyway before it
|
|
43
|
+
* edits anything. So the card inlines only the first kind and spends one
|
|
44
|
+
* whole block on pointers to the second.
|
|
45
|
+
*
|
|
46
|
+
* VENDOR NEUTRALITY
|
|
47
|
+
*
|
|
48
|
+
* Nothing here knows an IDE, a settings file or a hook event. The card is a
|
|
49
|
+
* string; the hook entry that carries it lives with the other peaks hook
|
|
50
|
+
* entries (`session-start-hook-constants.ts`) and the settings plumbing is
|
|
51
|
+
* `applyHookInstall`. The only input that touches the outside world is
|
|
52
|
+
* `projectRoot`.
|
|
53
|
+
*
|
|
54
|
+
* FAIL-SOFT IS A HARD REQUIREMENT, AND IT HAS A SHARP EDGE HERE
|
|
55
|
+
*
|
|
56
|
+
* The hook's stdout IS context. So an error message printed on stdout is not
|
|
57
|
+
* a visible failure — it is a failure injected straight into the model's
|
|
58
|
+
* context as if peaks-loop had meant it. `buildPostCompactReinjectionCard`
|
|
59
|
+
* therefore never throws (every source is individually guarded and reports
|
|
60
|
+
* into `unresolved`), and the CLI wrapper that renders it prints NOTHING and
|
|
61
|
+
* exits 0 when the card cannot be built.
|
|
62
|
+
*/
|
|
63
|
+
import { readdirSync, statSync } from 'node:fs';
|
|
64
|
+
import { join } from 'node:path';
|
|
65
|
+
import { readJobShapeDecision } from '../code/job-shape-decision.js';
|
|
66
|
+
import { tryReadJobProgress } from '../job/job-progress-store.js';
|
|
67
|
+
import { getSessionIdCanonical } from '../session/session-manager.js';
|
|
68
|
+
/**
|
|
69
|
+
* Hard ceiling on the card's size, in UTF-8 bytes.
|
|
70
|
+
*
|
|
71
|
+
* WHY 3072. Three numbers decided it, not taste:
|
|
72
|
+
*
|
|
73
|
+
* 1. It must be small against the freed space. A compaction releases tens
|
|
74
|
+
* of thousands of tokens. 3072 bytes is under ~1200 tokens of English —
|
|
75
|
+
* under 0.6% of a 200K window and under 0.12% of a 1M one. At that size
|
|
76
|
+
* the card cannot plausibly re-fill what the compaction freed, whatever
|
|
77
|
+
* the window, which is the property that makes the re-injection safe to
|
|
78
|
+
* run on every cycle.
|
|
79
|
+
* 2. It must be big enough for the whole content contract. Measured against
|
|
80
|
+
* this repository the full card — identity, pointers, rules, next action
|
|
81
|
+
* and progress — lands around 1.2-1.7 KiB. 3072 leaves roughly 2x
|
|
82
|
+
* headroom for long session ids, a long job id and a long absolute
|
|
83
|
+
* project root before the drop rule has to fire at all.
|
|
84
|
+
* 3. It must be a BYTE count rather than a token or percentage count. Bytes
|
|
85
|
+
* are computable on the hook path with `Buffer.byteLength` and no
|
|
86
|
+
* tokenizer, no window resolution and no dependency; and unlike a
|
|
87
|
+
* share-of-window budget, a fixed byte cap cannot silently GROW on a
|
|
88
|
+
* large-window model — which would reintroduce the very "re-fill the
|
|
89
|
+
* freed space" failure the cap exists to prevent. A percentage budget
|
|
90
|
+
* was considered and rejected for that reason.
|
|
91
|
+
*/
|
|
92
|
+
export const POST_COMPACT_REINJECTION_BYTE_BUDGET = 3072;
|
|
93
|
+
/**
|
|
94
|
+
* Runtime rules that live ONLY in the message history, so a compaction
|
|
95
|
+
* destroys them.
|
|
96
|
+
*
|
|
97
|
+
* Why a repo-owned constant and not something parsed out of the dispatch
|
|
98
|
+
* record: the dispatch record is free-form prose written per slice, and a
|
|
99
|
+
* parser over it would silently stop matching the day someone rephrases a
|
|
100
|
+
* heading — a rule that vanishes without a failure. These six are peaks-loop's
|
|
101
|
+
* OWN operating rules for an agent working in a peaks-loop session; keeping
|
|
102
|
+
* them here makes adding one a one-line change with a test behind it.
|
|
103
|
+
*
|
|
104
|
+
* The membership test for an entry is: "if the model forgets this, does it
|
|
105
|
+
* act WRONGLY on the next turn?" Session-general advice that already lives in
|
|
106
|
+
* CLAUDE.md / the system prompt is deliberately absent — the system prompt is
|
|
107
|
+
* not part of the summarized history, so re-injecting it would spend budget
|
|
108
|
+
* to say something the model can already see.
|
|
109
|
+
*/
|
|
110
|
+
export const AGENT_RUNTIME_RULES = Object.freeze([
|
|
111
|
+
'CLI: run `node --import tsx src/cli/index.ts <cmd>`, never `pnpm exec tsx` — on Windows the latter truncates any argument at its first newline.',
|
|
112
|
+
'Test scope: one file or pattern per run; the full suite requires PEAKS_FULL_TEST=1.',
|
|
113
|
+
'Exit codes: never read a test result through a pipe (`| tail` reports tail\'s status); read ${PIPESTATUS[0]}.',
|
|
114
|
+
'Never write `.claude/settings.local.json` from a slice — reproduce against a temp project copy via `--project <tmpdir>`.',
|
|
115
|
+
'Git: commit / push policy is per-dispatch and is NOT inherited from this card — re-read the dispatch record before any state-mutating git command.',
|
|
116
|
+
'No interactive questions: decide, act, and record the decision in the artifact.'
|
|
117
|
+
]);
|
|
118
|
+
/**
|
|
119
|
+
* The block ranks, in survival order. The drop rule reads THIS list, so it is
|
|
120
|
+
* the single place the priority is stated.
|
|
121
|
+
*
|
|
122
|
+
* The ordering principle is value-per-byte, highest first:
|
|
123
|
+
* 0 IDENTITY — which session; ~1 line, and every other line depends on it
|
|
124
|
+
* 1 WHERE TO READ — the pointers; cheap, always current, and the only way the
|
|
125
|
+
* model reaches the bulky material it must not have inlined
|
|
126
|
+
* 2 RULES — behaviour-changing, and unavailable anywhere else in context
|
|
127
|
+
* 3 NEXT — one line; without it the next turn has no target
|
|
128
|
+
* 4 CURRENT WORK — request / job / change identity and progress
|
|
129
|
+
* RULES outranks CURRENT WORK deliberately: the identity of the slice is
|
|
130
|
+
* recoverable by Reading the artifact the card points at, whereas a rule the
|
|
131
|
+
* model has forgotten will be broken before it thinks to go looking.
|
|
132
|
+
*/
|
|
133
|
+
export const REINJECTION_BLOCK_RANKS = Object.freeze([0, 1, 2, 3, 4]);
|
|
134
|
+
export const REINJECTION_BLOCK_HEADINGS = Object.freeze({
|
|
135
|
+
0: '[peaks-loop] post-compact state card',
|
|
136
|
+
1: 'WHERE TO READ',
|
|
137
|
+
2: 'RULES THAT ARE NOT IN CONTEXT ANY MORE',
|
|
138
|
+
3: 'NEXT',
|
|
139
|
+
4: 'CURRENT WORK'
|
|
140
|
+
});
|
|
141
|
+
/** Short names for the omitted-marker. See `ReinjectionBlock.label`. */
|
|
142
|
+
export const REINJECTION_BLOCK_LABELS = Object.freeze({
|
|
143
|
+
0: 'header',
|
|
144
|
+
1: 'pointers',
|
|
145
|
+
2: 'rules',
|
|
146
|
+
3: 'next',
|
|
147
|
+
4: 'current-work'
|
|
148
|
+
});
|
|
149
|
+
function byteLength(value) {
|
|
150
|
+
return Buffer.byteLength(value, 'utf8');
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Render one block: a heading line, then the body lines indented two spaces.
|
|
154
|
+
* A block is the smallest unit the drop rule can remove — it is never split,
|
|
155
|
+
* because half a pointer list or half a rule reads as a complete answer and
|
|
156
|
+
* is worse than an absent one.
|
|
157
|
+
*/
|
|
158
|
+
function renderBlock(block) {
|
|
159
|
+
const body = block.lines.map((line) => ` ${line}`).join('\n');
|
|
160
|
+
return body.length > 0 ? `${block.heading}\n${body}` : block.heading;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Assemble the card under a byte budget.
|
|
164
|
+
*
|
|
165
|
+
* THE RULE, stated so it can be checked rather than interpreted:
|
|
166
|
+
*
|
|
167
|
+
* 1. Blocks are rendered whole. Ranks are read in ascending order.
|
|
168
|
+
* 2. A block is emitted iff its bytes still fit in the remaining budget.
|
|
169
|
+
* Otherwise it is SKIPPED and the walk CONTINUES — greedy by rank, not
|
|
170
|
+
* stop-at-first-miss. This is the only one of the two candidate rules
|
|
171
|
+
* that keeps the survival order meaningful: with stop-at-first-miss, one
|
|
172
|
+
* oversized low-priority block would evict every block after it,
|
|
173
|
+
* including the hard rules. Skipping cannot reorder anything, because
|
|
174
|
+
* ranks are still walked in order.
|
|
175
|
+
* 3. Blocks are joined by a blank line, which is counted as part of the
|
|
176
|
+
* block being added (a separator is never emitted for a block that did
|
|
177
|
+
* not fit).
|
|
178
|
+
* 4. If anything was dropped and the remaining budget admits it, one final
|
|
179
|
+
* line names the dropped ranks. It is ALL-OR-NOTHING: it is emitted only
|
|
180
|
+
* if it fits entirely, so no string is ever truncated mid-way and the
|
|
181
|
+
* byte accounting stays exact.
|
|
182
|
+
*
|
|
183
|
+
* Deterministic in the strict sense: the output is a pure function of
|
|
184
|
+
* (blocks, budgetBytes). No clock, no filesystem, no "best effort".
|
|
185
|
+
*
|
|
186
|
+
* `budgetBytes` is injectable so the rule above is testable at any size; it
|
|
187
|
+
* defaults to the declared ceiling and no production caller overrides it.
|
|
188
|
+
*/
|
|
189
|
+
export function renderReinjectionCard(input) {
|
|
190
|
+
const budget = input.budgetBytes ?? POST_COMPACT_REINJECTION_BYTE_BUDGET;
|
|
191
|
+
const ordered = [...input.blocks].sort((a, b) => a.rank - b.rank);
|
|
192
|
+
const parts = [];
|
|
193
|
+
const emittedRanks = [];
|
|
194
|
+
const droppedRanks = [];
|
|
195
|
+
let used = 0;
|
|
196
|
+
for (const block of ordered) {
|
|
197
|
+
const rendered = renderBlock(block);
|
|
198
|
+
// A separator only ever counts once a block already exists, and it is
|
|
199
|
+
// charged to the block being added so a skipped block leaves no residue.
|
|
200
|
+
const cost = byteLength(rendered) + (parts.length > 0 ? 2 : 0);
|
|
201
|
+
if (used + cost > budget) {
|
|
202
|
+
droppedRanks.push(block.rank);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
parts.push(rendered);
|
|
206
|
+
emittedRanks.push(block.rank);
|
|
207
|
+
used += cost;
|
|
208
|
+
}
|
|
209
|
+
if (droppedRanks.length > 0) {
|
|
210
|
+
const labelsByRank = new Map(ordered.map((b) => [b.rank, b.label]));
|
|
211
|
+
const names = droppedRanks.map((rank) => labelsByRank.get(rank) ?? `rank ${rank}`).join(', ');
|
|
212
|
+
const marker = `[peaks-loop] omitted (budget ${budget}B): ${names}`;
|
|
213
|
+
const cost = byteLength(marker) + (parts.length > 0 ? 2 : 0);
|
|
214
|
+
if (used + cost <= budget) {
|
|
215
|
+
parts.push(marker);
|
|
216
|
+
used += cost;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const text = parts.join('\n\n');
|
|
220
|
+
return {
|
|
221
|
+
text,
|
|
222
|
+
bytes: byteLength(text),
|
|
223
|
+
budgetBytes: budget,
|
|
224
|
+
emittedRanks,
|
|
225
|
+
droppedRanks
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/** Forward-slash a path so the card's pointers are shell-dialect neutral. */
|
|
229
|
+
function toPosix(p) {
|
|
230
|
+
return p.replaceAll('\\', '/');
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Find the most recently modified request artifact under the session tree.
|
|
234
|
+
*
|
|
235
|
+
* This is a POINTER RESOLVER, not a content reader: it returns the artifact's
|
|
236
|
+
* name so the card can name it, and never opens it. Deliberately limited to
|
|
237
|
+
* `<session>/<role>/requests/*.md` — the tree every role already writes to —
|
|
238
|
+
* rather than a repo-wide search, so the answer cannot be some unrelated
|
|
239
|
+
* markdown file that happens to be newer.
|
|
240
|
+
*
|
|
241
|
+
* Returns its failures alongside the answer instead of swallowing them. The
|
|
242
|
+
* tempting shape here — a bare `catch { continue }` — makes two very different
|
|
243
|
+
* states indistinguishable to the caller: "this role has no requests yet"
|
|
244
|
+
* (ordinary, most roles) and "this role's requests directory exists but could
|
|
245
|
+
* not be read" (a real problem worth reporting). Only `ENOENT` is the ordinary
|
|
246
|
+
* one, so only `ENOENT` is silent.
|
|
247
|
+
*/
|
|
248
|
+
function findLatestRequestArtifact(projectRoot, sessionId) {
|
|
249
|
+
const sessionDir = join(projectRoot, '.peaks', '_runtime', sessionId);
|
|
250
|
+
const warnings = [];
|
|
251
|
+
let roles;
|
|
252
|
+
try {
|
|
253
|
+
roles = readdirSync(sessionDir, { withFileTypes: true })
|
|
254
|
+
.filter((entry) => entry.isDirectory())
|
|
255
|
+
.map((entry) => entry.name)
|
|
256
|
+
.sort();
|
|
257
|
+
}
|
|
258
|
+
catch (err) {
|
|
259
|
+
// No session tree at all is the ordinary "nothing to point at" case; any
|
|
260
|
+
// other failure is reported.
|
|
261
|
+
if (errnoCode(err) !== 'ENOENT')
|
|
262
|
+
warnings.push(`session tree unreadable: ${sessionId}`);
|
|
263
|
+
return { rel: null, warnings };
|
|
264
|
+
}
|
|
265
|
+
let best = null;
|
|
266
|
+
for (const role of roles) {
|
|
267
|
+
const requestsDir = join(sessionDir, role, 'requests');
|
|
268
|
+
let files;
|
|
269
|
+
try {
|
|
270
|
+
files = readdirSync(requestsDir).filter((name) => name.endsWith('.md'));
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
if (errnoCode(err) !== 'ENOENT')
|
|
274
|
+
warnings.push(`requests dir unreadable: ${role}/requests`);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
for (const name of files) {
|
|
278
|
+
try {
|
|
279
|
+
const mtimeMs = statSync(join(requestsDir, name)).mtimeMs;
|
|
280
|
+
if (best === null || mtimeMs > best.mtimeMs) {
|
|
281
|
+
best = { rel: `${role}/requests/${name}`, mtimeMs };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
// A single unreadable artifact must not hide the rest of the tree, but
|
|
286
|
+
// it must not disappear either — a rename/delete racing this read is
|
|
287
|
+
// the likely cause and it is worth knowing the answer is partial.
|
|
288
|
+
warnings.push(`request artifact unreadable: ${role}/requests/${name}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return { rel: best === null ? null : best.rel, warnings };
|
|
293
|
+
}
|
|
294
|
+
/** The POSIX errno code on a thrown value, or `undefined` for anything else. */
|
|
295
|
+
function errnoCode(err) {
|
|
296
|
+
const code = err?.code;
|
|
297
|
+
return typeof code === 'string' ? code : undefined;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Read every available fact. Never throws: each source is guarded separately
|
|
301
|
+
* and a failure is recorded in `unresolved` rather than propagated, because
|
|
302
|
+
* the caller is a session-start hook.
|
|
303
|
+
*/
|
|
304
|
+
export function resolvePostCompactReinjectionFacts(input) {
|
|
305
|
+
const projectRoot = input.projectRoot;
|
|
306
|
+
const unresolved = [];
|
|
307
|
+
let sessionId = input.sessionId ?? null;
|
|
308
|
+
if (sessionId === null) {
|
|
309
|
+
try {
|
|
310
|
+
sessionId = getSessionIdCanonical(projectRoot);
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
sessionId = null;
|
|
314
|
+
}
|
|
315
|
+
if (sessionId === null)
|
|
316
|
+
unresolved.push('session-id (no canonical binding)');
|
|
317
|
+
}
|
|
318
|
+
let jobId = null;
|
|
319
|
+
let isJob = false;
|
|
320
|
+
if (sessionId !== null) {
|
|
321
|
+
try {
|
|
322
|
+
const record = readJobShapeDecision(projectRoot, sessionId);
|
|
323
|
+
isJob = record.decision.isJob;
|
|
324
|
+
jobId = record.decision.suggestedJobId;
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
unresolved.push('job-shape (not decided for this session)');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
let progress = null;
|
|
331
|
+
if (sessionId !== null && jobId !== null) {
|
|
332
|
+
try {
|
|
333
|
+
const read = tryReadJobProgress(projectRoot, sessionId, jobId);
|
|
334
|
+
if (read === null) {
|
|
335
|
+
unresolved.push('job-progress (no progress.json yet)');
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
progress = { done: read.done, total: read.total, currentSlice: read.currentSlice };
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
unresolved.push('job-progress (unreadable)');
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
let latestRequest = null;
|
|
346
|
+
if (sessionId !== null) {
|
|
347
|
+
try {
|
|
348
|
+
const found = findLatestRequestArtifact(projectRoot, sessionId);
|
|
349
|
+
latestRequest = found.rel;
|
|
350
|
+
for (const warning of found.warnings)
|
|
351
|
+
unresolved.push(`latest-request (${warning})`);
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
// The resolver reports its own failures; this is only for the
|
|
355
|
+
// unforeseen, and it is still reported rather than silently dropped.
|
|
356
|
+
unresolved.push('latest-request (resolver threw)');
|
|
357
|
+
}
|
|
358
|
+
if (latestRequest === null)
|
|
359
|
+
unresolved.push('latest-request (none on disk)');
|
|
360
|
+
}
|
|
361
|
+
return { projectRoot, sessionId, jobId, isJob, progress, latestRequest, unresolved };
|
|
362
|
+
}
|
|
363
|
+
function buildBlocks(facts) {
|
|
364
|
+
const root = toPosix(facts.projectRoot);
|
|
365
|
+
const blocks = [];
|
|
366
|
+
// Rank 0 — identity.
|
|
367
|
+
const identity = [`project: ${root}`];
|
|
368
|
+
identity.push(`session: ${facts.sessionId ?? 'unbound'}`);
|
|
369
|
+
blocks.push({
|
|
370
|
+
rank: 0,
|
|
371
|
+
heading: REINJECTION_BLOCK_HEADINGS[0] ?? 'state',
|
|
372
|
+
label: REINJECTION_BLOCK_LABELS[0] ?? 'rank-0',
|
|
373
|
+
lines: identity
|
|
374
|
+
});
|
|
375
|
+
// Rank 1 — pointers. Relative paths: the model's cwd is the project root,
|
|
376
|
+
// so a relative path is both shorter and stable across machines.
|
|
377
|
+
const pointers = [];
|
|
378
|
+
if (facts.sessionId !== null) {
|
|
379
|
+
const base = `.peaks/_runtime/${facts.sessionId}`;
|
|
380
|
+
pointers.push(`session tree: ${base}/`);
|
|
381
|
+
pointers.push(`requests: read the newest file under ${base}/<role>/requests/`);
|
|
382
|
+
if (facts.jobId !== null)
|
|
383
|
+
pointers.push(`job progress: ${base}/job/${facts.jobId}/progress.json`);
|
|
384
|
+
pointers.push(`job shape: ${base}/job-shape.json`);
|
|
385
|
+
pointers.push(`dispatch records: .peaks/_sub_agents/${facts.sessionId}/dispatch-*.json`);
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
pointers.push('session tree: unbound — run `peaks workspace init --project .` first');
|
|
389
|
+
}
|
|
390
|
+
pointers.push('project memory: .peaks/memory/ (read the index before assuming)');
|
|
391
|
+
blocks.push({
|
|
392
|
+
rank: 1,
|
|
393
|
+
heading: REINJECTION_BLOCK_HEADINGS[1] ?? 'where to read',
|
|
394
|
+
label: REINJECTION_BLOCK_LABELS[1] ?? 'rank-1',
|
|
395
|
+
lines: pointers
|
|
396
|
+
});
|
|
397
|
+
// Rank 2 — the rules that only exist in the summarized history.
|
|
398
|
+
blocks.push({
|
|
399
|
+
rank: 2,
|
|
400
|
+
heading: REINJECTION_BLOCK_HEADINGS[2] ?? 'rules',
|
|
401
|
+
label: REINJECTION_BLOCK_LABELS[2] ?? 'rank-2',
|
|
402
|
+
lines: [...AGENT_RUNTIME_RULES]
|
|
403
|
+
});
|
|
404
|
+
// Rank 3 — next action.
|
|
405
|
+
const next = [];
|
|
406
|
+
if (facts.progress !== null) {
|
|
407
|
+
next.push(`job ${facts.jobId ?? '?'}: slice ${facts.progress.done + 1}/${facts.progress.total} (${facts.progress.currentSlice})`);
|
|
408
|
+
}
|
|
409
|
+
else if (facts.isJob) {
|
|
410
|
+
next.push(`job ${facts.jobId ?? '?'}: no progress.json yet — resume at the first unfinished slice`);
|
|
411
|
+
}
|
|
412
|
+
else if (facts.latestRequest !== null) {
|
|
413
|
+
// NOT "continue <path>". The newest artifact is the best available guess
|
|
414
|
+
// at the live request, but it is only a guess — a sibling role writing to
|
|
415
|
+
// its own `requests/` directory bumps that directory's mtimes too, so the
|
|
416
|
+
// card names the candidate and sends the model to the authority (the
|
|
417
|
+
// dispatch record) rather than promoting a heuristic into an instruction.
|
|
418
|
+
next.push(`no active job — confirm the live request from the dispatch record, then continue it`);
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
next.push('no job and no request on disk — re-read the user request before acting');
|
|
422
|
+
}
|
|
423
|
+
blocks.push({
|
|
424
|
+
rank: 3,
|
|
425
|
+
heading: REINJECTION_BLOCK_HEADINGS[3] ?? 'next',
|
|
426
|
+
label: REINJECTION_BLOCK_LABELS[3] ?? 'rank-3',
|
|
427
|
+
lines: next
|
|
428
|
+
});
|
|
429
|
+
// Rank 4 — current work identity + progress.
|
|
430
|
+
const current = [];
|
|
431
|
+
current.push(`session: ${facts.sessionId ?? 'unbound'}`);
|
|
432
|
+
if (facts.jobId !== null) {
|
|
433
|
+
current.push(`job: ${facts.jobId}${facts.isJob ? ' (job mode)' : ''}`);
|
|
434
|
+
}
|
|
435
|
+
if (facts.progress !== null) {
|
|
436
|
+
current.push(`progress: ${facts.progress.done}/${facts.progress.total} — current ${facts.progress.currentSlice}`);
|
|
437
|
+
}
|
|
438
|
+
if (facts.latestRequest !== null) {
|
|
439
|
+
current.push(`latest request artifact: ${facts.latestRequest}`);
|
|
440
|
+
}
|
|
441
|
+
blocks.push({
|
|
442
|
+
rank: 4,
|
|
443
|
+
heading: REINJECTION_BLOCK_HEADINGS[4] ?? 'current work',
|
|
444
|
+
label: REINJECTION_BLOCK_LABELS[4] ?? 'rank-4',
|
|
445
|
+
lines: current
|
|
446
|
+
});
|
|
447
|
+
return blocks;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Build the card for a project. This is the whole production surface: read
|
|
451
|
+
* the facts, render them under the budget, return. NEVER throws — a thrown
|
|
452
|
+
* error here would surface on session start, which is the one place the
|
|
453
|
+
* absence of this feature must not be able to break anything.
|
|
454
|
+
*/
|
|
455
|
+
export function buildPostCompactReinjectionCard(input) {
|
|
456
|
+
const budgetBytes = input.budgetBytes ?? POST_COMPACT_REINJECTION_BYTE_BUDGET;
|
|
457
|
+
let facts;
|
|
458
|
+
try {
|
|
459
|
+
facts = resolvePostCompactReinjectionFacts({
|
|
460
|
+
projectRoot: input.projectRoot,
|
|
461
|
+
sessionId: input.sessionId ?? null
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
// The resolver is guarded source-by-source; this is the belt-and-braces
|
|
466
|
+
// path for anything unforeseen. An empty card is the correct outcome —
|
|
467
|
+
// the hook then contributes nothing to the context, which is exactly
|
|
468
|
+
// what a session with no state to re-inject should contribute.
|
|
469
|
+
return {
|
|
470
|
+
ok: false,
|
|
471
|
+
text: '',
|
|
472
|
+
bytes: 0,
|
|
473
|
+
budgetBytes,
|
|
474
|
+
sessionId: null,
|
|
475
|
+
emittedRanks: [],
|
|
476
|
+
droppedRanks: [],
|
|
477
|
+
unresolved: ['facts-resolution-threw']
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
const card = renderReinjectionCard({ blocks: buildBlocks(facts), budgetBytes });
|
|
481
|
+
return {
|
|
482
|
+
ok: true,
|
|
483
|
+
text: card.text,
|
|
484
|
+
bytes: card.bytes,
|
|
485
|
+
budgetBytes: card.budgetBytes,
|
|
486
|
+
sessionId: facts.sessionId,
|
|
487
|
+
emittedRanks: card.emittedRanks,
|
|
488
|
+
droppedRanks: card.droppedRanks,
|
|
489
|
+
unresolved: facts.unresolved
|
|
490
|
+
};
|
|
491
|
+
}
|
|
@@ -21,7 +21,7 @@ function readRegistrations(input) {
|
|
|
21
21
|
}
|
|
22
22
|
function captureConflictDiff(input) {
|
|
23
23
|
try {
|
|
24
|
-
return execFileSync('git', ['diff', '--merge', '--no-color'], { cwd: input.projectRoot, encoding: 'utf8' });
|
|
24
|
+
return execFileSync('git', ['diff', '--merge', '--no-color'], { cwd: input.projectRoot, encoding: 'utf8', windowsHide: true });
|
|
25
25
|
}
|
|
26
26
|
catch {
|
|
27
27
|
return '';
|
|
@@ -29,7 +29,7 @@ function captureConflictDiff(input) {
|
|
|
29
29
|
}
|
|
30
30
|
function captureTranscript(input) {
|
|
31
31
|
try {
|
|
32
|
-
return execFileSync('git', ['merge', '--no-edit', '--no-ff', input.agentBranch], { cwd: input.projectRoot, encoding: 'utf8' }).split('\n');
|
|
32
|
+
return execFileSync('git', ['merge', '--no-edit', '--no-ff', input.agentBranch], { cwd: input.projectRoot, encoding: 'utf8', windowsHide: true }).split('\n');
|
|
33
33
|
}
|
|
34
34
|
catch {
|
|
35
35
|
return ['git merge --no-edit --no-ff ' + input.agentBranch];
|
|
@@ -61,8 +61,8 @@ export async function runMergeBack(input) {
|
|
|
61
61
|
continue;
|
|
62
62
|
}
|
|
63
63
|
try {
|
|
64
|
-
execFileSync('git', ['checkout', input.callerBranch], { cwd: input.projectRoot, stdio: 'ignore' });
|
|
65
|
-
execFileSync(plan.command[0], plan.command.slice(1), { cwd: input.projectRoot, stdio: 'ignore' });
|
|
64
|
+
execFileSync('git', ['checkout', input.callerBranch], { cwd: input.projectRoot, stdio: 'ignore', windowsHide: true });
|
|
65
|
+
execFileSync(plan.command[0], plan.command.slice(1), { cwd: input.projectRoot, stdio: 'ignore', windowsHide: true });
|
|
66
66
|
return { kind: 'merged', attempts, serviceKills: kills };
|
|
67
67
|
}
|
|
68
68
|
catch (error) {
|
|
@@ -71,7 +71,7 @@ export async function runMergeBack(input) {
|
|
|
71
71
|
const replay = buildConflictReplay({ originalPrompt, mergeAttemptTranscript: transcript, conflictDiff, callerBranch: input.callerBranch });
|
|
72
72
|
const replayResult = await input.onConflict(replay);
|
|
73
73
|
try {
|
|
74
|
-
execFileSync('git', ['merge', '--abort'], { cwd: input.projectRoot, stdio: 'ignore' });
|
|
74
|
+
execFileSync('git', ['merge', '--abort'], { cwd: input.projectRoot, stdio: 'ignore', windowsHide: true });
|
|
75
75
|
}
|
|
76
76
|
catch { /* ignore */ }
|
|
77
77
|
if (!replayResult.ok) {
|
|
@@ -30,16 +30,16 @@ export function killRegisteredServices(input) {
|
|
|
30
30
|
}
|
|
31
31
|
try {
|
|
32
32
|
if (platform === 'win32') {
|
|
33
|
-
execFileSync('taskkill', ['/T', '/F', '/PID', String(reg.pid)], { stdio: 'ignore' });
|
|
33
|
+
execFileSync('taskkill', ['/T', '/F', '/PID', String(reg.pid)], { stdio: 'ignore', windowsHide: true });
|
|
34
34
|
return { pid: reg.pid, name: reg.name, skipped: false, signal: 'taskkill' };
|
|
35
35
|
}
|
|
36
36
|
// POSIX: try SIGTERM via the `kill` CLI (universally available);
|
|
37
37
|
// escalate to SIGKILL on failure. The runner is best-effort.
|
|
38
38
|
try {
|
|
39
|
-
execFileSync('kill', ['-TERM', String(reg.pid)], { stdio: 'ignore' });
|
|
39
|
+
execFileSync('kill', ['-TERM', String(reg.pid)], { stdio: 'ignore', windowsHide: true });
|
|
40
40
|
}
|
|
41
41
|
catch {
|
|
42
|
-
execFileSync('kill', ['-KILL', String(reg.pid)], { stdio: 'ignore' });
|
|
42
|
+
execFileSync('kill', ['-KILL', String(reg.pid)], { stdio: 'ignore', windowsHide: true });
|
|
43
43
|
return { pid: reg.pid, name: reg.name, skipped: false, signal: 'SIGKILL' };
|
|
44
44
|
}
|
|
45
45
|
return { pid: reg.pid, name: reg.name, skipped: false, signal: 'SIGTERM' };
|
|
@@ -91,7 +91,8 @@ export function gitLogSince(projectRoot, since) {
|
|
|
91
91
|
const out = execSync(`git log --pretty=format:"%s" ${since}..HEAD`, {
|
|
92
92
|
cwd: projectRoot,
|
|
93
93
|
encoding: 'utf8',
|
|
94
|
-
stdio: ['ignore', 'pipe', 'ignore']
|
|
94
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
95
|
+
windowsHide: true
|
|
95
96
|
});
|
|
96
97
|
if (out.trim().length === 0)
|
|
97
98
|
return [];
|
|
@@ -44,7 +44,7 @@ const defaultRunner = {
|
|
|
44
44
|
resolveRun(value);
|
|
45
45
|
};
|
|
46
46
|
try {
|
|
47
|
-
const child = spawn(command, args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
47
|
+
const child = spawn(command, args, { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
|
|
48
48
|
let stdout = '';
|
|
49
49
|
child.stdout?.on('data', (chunk) => {
|
|
50
50
|
stdout += chunk.toString('utf8');
|