headlesscode 1.0.2
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/ATTRIBUTION.md +53 -0
- package/CODE_OF_CONDUCT.md +130 -0
- package/CONTRIBUTING.md +107 -0
- package/LICENSE +202 -0
- package/README.md +486 -0
- package/SECURITY.md +211 -0
- package/bin/headlesscode.mjs +83 -0
- package/package.json +63 -0
- package/shared/prompts/review-mode-prompt-short.md +93 -0
- package/shared/prompts/review-mode-prompt.md +281 -0
- package/shared/rules-code/rules.md +22 -0
- package/shared/stacks/cpp/rules.md +30 -0
- package/shared/stacks/fastapi/rules.md +30 -0
- package/shared/stacks/javascript/rules.md +37 -0
- package/shared/stacks/postgresql/rules.md +31 -0
- package/shared/stacks/python/rules.md +35 -0
- package/shared/stacks/react/rules.md +11 -0
- package/shared/stacks/typescript/rules.md +10 -0
- package/src/budget/budget.ts +221 -0
- package/src/budget/concurrency.ts +126 -0
- package/src/budget/cost.ts +309 -0
- package/src/budget/index.ts +8 -0
- package/src/checkpoints/cli.ts +256 -0
- package/src/checkpoints/service.ts +227 -0
- package/src/cli.ts +1535 -0
- package/src/cloud/docker-provider.ts +334 -0
- package/src/cloud/provider.ts +300 -0
- package/src/codeintel/call-graph.ts +78 -0
- package/src/codeintel/find-references.ts +123 -0
- package/src/codeintel/go-to-definition.ts +193 -0
- package/src/codeintel/handlers.ts +190 -0
- package/src/codeintel/import-graph.ts +173 -0
- package/src/codeintel/outline.ts +180 -0
- package/src/codeintel/position.ts +77 -0
- package/src/codeintel/program.ts +350 -0
- package/src/codeintel/rename-symbol.ts +213 -0
- package/src/codeintel/tools.ts +280 -0
- package/src/codemap/build.ts +135 -0
- package/src/codemap/cli.ts +190 -0
- package/src/codemap/extract.ts +339 -0
- package/src/codemap/files.ts +236 -0
- package/src/codemap/fingerprint.ts +65 -0
- package/src/codemap/flows.ts +62 -0
- package/src/codemap/html.ts +451 -0
- package/src/codemap/lock.ts +80 -0
- package/src/codemap/types.ts +101 -0
- package/src/codesearch/airunner-embedder.ts +185 -0
- package/src/codesearch/chunk.ts +339 -0
- package/src/codesearch/cli.ts +223 -0
- package/src/codesearch/embedder.ts +332 -0
- package/src/codesearch/files.ts +280 -0
- package/src/codesearch/index.ts +469 -0
- package/src/codesearch/ollama-embedder.ts +205 -0
- package/src/codesearch/search.ts +141 -0
- package/src/codesearch/types.ts +100 -0
- package/src/config/mode-models.ts +218 -0
- package/src/dashboard/aggregate.ts +364 -0
- package/src/dashboard/chat-thread.ts +141 -0
- package/src/dashboard/checkpoints.ts +124 -0
- package/src/dashboard/cli.ts +193 -0
- package/src/dashboard/codemap.ts +44 -0
- package/src/dashboard/files.ts +121 -0
- package/src/dashboard/page.ts +2803 -0
- package/src/dashboard/self-improvement-metrics.ts +282 -0
- package/src/dashboard/server.ts +1103 -0
- package/src/dashboard/session-launch.ts +310 -0
- package/src/dashboard/timeline.ts +273 -0
- package/src/dashboard/tool-exec.ts +107 -0
- package/src/dashboard/trend-cli.ts +141 -0
- package/src/dashboard/trend.ts +413 -0
- package/src/decision-proxy/cli.ts +261 -0
- package/src/decision-proxy/proxy.ts +569 -0
- package/src/deploy/gate-cli.ts +147 -0
- package/src/deploy/gate.ts +254 -0
- package/src/engine/condense.ts +512 -0
- package/src/engine/events.ts +428 -0
- package/src/engine/handoff.ts +71 -0
- package/src/engine/lazy-tools.ts +160 -0
- package/src/engine/local-explore.ts +653 -0
- package/src/engine/logger.ts +96 -0
- package/src/engine/loop.ts +5517 -0
- package/src/engine/parser.ts +347 -0
- package/src/engine/prompt.ts +860 -0
- package/src/engine/reports.ts +47 -0
- package/src/engine/stacks.ts +448 -0
- package/src/engine/types.ts +291 -0
- package/src/engine/usage.ts +186 -0
- package/src/github/app-auth.ts +161 -0
- package/src/github/cli.ts +448 -0
- package/src/github/installations.ts +133 -0
- package/src/github/pr.ts +321 -0
- package/src/github/provision.ts +118 -0
- package/src/github/push.ts +122 -0
- package/src/index-util.ts +50 -0
- package/src/index.ts +81 -0
- package/src/init/cli.ts +248 -0
- package/src/init/gitignore.ts +74 -0
- package/src/llm/ollama.ts +308 -0
- package/src/llm/openrouter.ts +868 -0
- package/src/llm/preflight.ts +367 -0
- package/src/llm/transcript-capture.ts +84 -0
- package/src/memory/embed.ts +110 -0
- package/src/memory/index.ts +22 -0
- package/src/memory/local.ts +259 -0
- package/src/memory/summarizer.ts +283 -0
- package/src/memory/types.ts +153 -0
- package/src/memory/uwuchat.ts +157 -0
- package/src/migrate/cli.ts +115 -0
- package/src/orchestrator/analyze-cli.ts +104 -0
- package/src/orchestrator/auto-split.ts +206 -0
- package/src/orchestrator/cleanup.ts +1003 -0
- package/src/orchestrator/cli.ts +3571 -0
- package/src/orchestrator/cost-estimate.ts +564 -0
- package/src/orchestrator/cost-history-cli.ts +242 -0
- package/src/orchestrator/cost-history.ts +397 -0
- package/src/orchestrator/git-sync.ts +250 -0
- package/src/orchestrator/index.ts +153 -0
- package/src/orchestrator/log-analysis.ts +0 -0
- package/src/orchestrator/merge-check.ts +108 -0
- package/src/orchestrator/pipeline.ts +411 -0
- package/src/orchestrator/resume.ts +1940 -0
- package/src/orchestrator/reviewer.ts +503 -0
- package/src/orchestrator/split.ts +296 -0
- package/src/orchestrator/state.ts +542 -0
- package/src/orchestrator/status.ts +697 -0
- package/src/orchestrator/verification-gate.ts +134 -0
- package/src/orchestrator/watch.ts +898 -0
- package/src/permissions/commands.ts +1083 -0
- package/src/permissions/config.ts +241 -0
- package/src/permissions/index.ts +12 -0
- package/src/permissions/protected-files.ts +96 -0
- package/src/permissions/store-protection.ts +272 -0
- package/src/project-store.ts +648 -0
- package/src/projects/cli.ts +382 -0
- package/src/qa/qa.ts +487 -0
- package/src/tools/browser/handler.ts +346 -0
- package/src/tools/browser/service.ts +406 -0
- package/src/tools/browser/smoke.ts +78 -0
- package/src/tools/browser/tool.ts +99 -0
- package/src/tools/executor.ts +2575 -0
- package/src/tools/language-detect.ts +183 -0
- package/src/tools/output-summarizer.ts +369 -0
- package/src/tools/run-tests.ts +302 -0
- package/src/tools/set-indentation-tool.ts +49 -0
- package/src/tools/test-selection.ts +160 -0
- package/src/vendor/tests/smoke.ts +103 -0
- package/src/vendor/zoo-code/VENDOR-NOTES.md +213 -0
- package/src/vendor/zoo-code/shim/anthropic.ts +71 -0
- package/src/vendor/zoo-code/shim/openai.d.ts +60 -0
- package/src/vendor/zoo-code/shim/os-name.ts +18 -0
- package/src/vendor/zoo-code/shim/strip-bom.ts +14 -0
- package/src/vendor/zoo-code/shim/vscode.ts +76 -0
- package/src/vendor/zoo-code/src/core/config/CustomModesManager.ts +1015 -0
- package/src/vendor/zoo-code/src/core/diff/strategies/multi-search-replace.ts +670 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/capabilities.ts +46 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/custom-instructions.ts +559 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/index.ts +10 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/markdown-formatting.ts +7 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/modes.ts +35 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/objective.ts +13 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/rules.ts +95 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/skills.ts +105 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/system-info.ts +30 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/tool-use-guidelines.ts +9 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/tool-use.ts +7 -0
- package/src/vendor/zoo-code/src/core/prompts/system.ts +176 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/access_mcp_resource.ts +41 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_diff.ts +40 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_patch.ts +61 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/ask_followup_question.ts +62 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/attempt_completion.ts +33 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/codebase_search.ts +43 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/converters.ts +109 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit.ts +48 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit_file.ts +72 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/execute_command.ts +54 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/generate_image.ts +51 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/index.ts +75 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/list_files.ts +41 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/mcp_server.ts +75 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/new_task.ts +39 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_command_output.ts +81 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_file.ts +169 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/run_slash_command.ts +31 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_files.ts +50 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_replace.ts +51 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/skill.ts +33 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/switch_mode.ts +31 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/update_todo_list.ts +54 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/write_to_file.ts +40 -0
- package/src/vendor/zoo-code/src/core/prompts/types.ts +12 -0
- package/src/vendor/zoo-code/src/i18n/index.ts +19 -0
- package/src/vendor/zoo-code/src/integrations/misc/extract-text.ts +81 -0
- package/src/vendor/zoo-code/src/services/checkpoints/RepoPerTaskCheckpointService.ts +15 -0
- package/src/vendor/zoo-code/src/services/checkpoints/ShadowCheckpointService.ts +553 -0
- package/src/vendor/zoo-code/src/services/checkpoints/excludes.ts +212 -0
- package/src/vendor/zoo-code/src/services/checkpoints/index.ts +3 -0
- package/src/vendor/zoo-code/src/services/checkpoints/types.ts +35 -0
- package/src/vendor/zoo-code/src/services/code-index/manager.ts +19 -0
- package/src/vendor/zoo-code/src/services/mcp/McpHub.ts +36 -0
- package/src/vendor/zoo-code/src/services/roo-config/index.ts +441 -0
- package/src/vendor/zoo-code/src/services/search/file-search.ts +143 -0
- package/src/vendor/zoo-code/src/services/skills/SkillsManager.ts +20 -0
- package/src/vendor/zoo-code/src/shared/globalFileNames.ts +9 -0
- package/src/vendor/zoo-code/src/shared/language.ts +43 -0
- package/src/vendor/zoo-code/src/shared/modes.ts +257 -0
- package/src/vendor/zoo-code/src/shared/tools.ts +385 -0
- package/src/vendor/zoo-code/src/utils/fs.ts +39 -0
- package/src/vendor/zoo-code/src/utils/globalContext.ts +22 -0
- package/src/vendor/zoo-code/src/utils/json-schema.ts +16 -0
- package/src/vendor/zoo-code/src/utils/logging.ts +21 -0
- package/src/vendor/zoo-code/src/utils/mcp-name.ts +190 -0
- package/src/vendor/zoo-code/src/utils/object.ts +18 -0
- package/src/vendor/zoo-code/src/utils/path.ts +94 -0
- package/src/vendor/zoo-code/src/utils/shell.ts +376 -0
- package/src/vendor/zoo-code/src/utils/text-normalization.ts +99 -0
- package/src/vendor/zoo-code/types/global-settings.ts +19 -0
- package/src/vendor/zoo-code/types/index.ts +22 -0
- package/src/vendor/zoo-code/types/message.ts +375 -0
- package/src/vendor/zoo-code/types/mode.ts +241 -0
- package/src/vendor/zoo-code/types/todo.ts +19 -0
- package/src/vendor/zoo-code/types/tool-params.ts +116 -0
- package/src/vendor/zoo-code/types/tool.ts +67 -0
- package/src/vendor/zoo-code/types/vscode.ts +84 -0
- package/src/vision/describe.ts +242 -0
- package/src/vision/tool.ts +91 -0
- package/src/watcher/cli.ts +369 -0
- package/src/watcher/github.ts +304 -0
- package/src/watcher/index.ts +59 -0
- package/src/watcher/state.ts +254 -0
- package/src/watcher/watch.ts +562 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 5 — GitHub issue watcher — public exports.
|
|
3
|
+
*
|
|
4
|
+
* The watcher polls a target label on a GitHub repo, fans each newly-seen
|
|
5
|
+
* issue out into a batch (splitIssues -> spawn via the existing bash
|
|
6
|
+
* spawner), and tracks idempotency durably in a state file so a restart
|
|
7
|
+
* never double-spawns. See docs/phase5-issue-watcher.md.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
watchIssues,
|
|
12
|
+
defaultSpawn,
|
|
13
|
+
DEFAULT_POLL_INTERVAL_MS,
|
|
14
|
+
DEFAULT_MAX_PER_SWEEP,
|
|
15
|
+
} from "./watch.js"
|
|
16
|
+
export type {
|
|
17
|
+
WatcherConfig,
|
|
18
|
+
WatcherBatchInfo,
|
|
19
|
+
SpawnBatch,
|
|
20
|
+
SpawnResult,
|
|
21
|
+
SweepResult,
|
|
22
|
+
WatchResult,
|
|
23
|
+
} from "./watch.js"
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
loadWatcherState,
|
|
27
|
+
loadWatcherStateSync,
|
|
28
|
+
saveWatcherState,
|
|
29
|
+
saveWatcherStateSync,
|
|
30
|
+
markProcessed,
|
|
31
|
+
isProcessed,
|
|
32
|
+
defaultWatcherState,
|
|
33
|
+
} from "./state.js"
|
|
34
|
+
export type { WatcherIssueEntry, WatcherIssueStatus, WatcherState } from "./state.js"
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
ghApi,
|
|
38
|
+
buildGhUrl,
|
|
39
|
+
listIssues,
|
|
40
|
+
getIssue,
|
|
41
|
+
addLabel,
|
|
42
|
+
createGhClient,
|
|
43
|
+
toIssue,
|
|
44
|
+
GhApiError,
|
|
45
|
+
DEFAULT_GH_BASE_URL,
|
|
46
|
+
GH_API_VERSION,
|
|
47
|
+
ISSUES_PAGE_SIZE,
|
|
48
|
+
DEFAULT_MAX_ISSUES,
|
|
49
|
+
} from "./github.js"
|
|
50
|
+
export type {
|
|
51
|
+
GhClient,
|
|
52
|
+
GhClientOptions,
|
|
53
|
+
GhRequestOptions,
|
|
54
|
+
GitHubIssue,
|
|
55
|
+
ListIssuesOptions,
|
|
56
|
+
} from "./github.js"
|
|
57
|
+
|
|
58
|
+
export { watchMain, parseWatchArgs } from "./cli.js"
|
|
59
|
+
export type { WatchCliOptions } from "./cli.js"
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 5 — watcher durable idempotency state — read/write
|
|
3
|
+
* `.worktrees/.watcher-state.json` (path overridable).
|
|
4
|
+
*
|
|
5
|
+
* Schema:
|
|
6
|
+
*
|
|
7
|
+
* {
|
|
8
|
+
* "processed": {
|
|
9
|
+
* "<issueNumber>": {
|
|
10
|
+
* "number": 27,
|
|
11
|
+
* "title": "…",
|
|
12
|
+
* "label": "needs-agent", // the target label that triggered it
|
|
13
|
+
* "batch": "watcher-2026-08-01",
|
|
14
|
+
* "groups": [ { name, issues, taskFile } ], // splitIssues output
|
|
15
|
+
* "spawnedAt": ISO timestamp, // when the batch was (or will be) spawned
|
|
16
|
+
* "status": "spawned"|"done"|"failed"|"skipped"|"pending",
|
|
17
|
+
* "updatedAt": ISO timestamp,
|
|
18
|
+
* "error": "…" // only on failed
|
|
19
|
+
* }
|
|
20
|
+
* },
|
|
21
|
+
* "updated": ISO timestamp,
|
|
22
|
+
* "lastSweep": ISO timestamp
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* Idempotency semantics: an issue is "already processed" iff it has an entry
|
|
26
|
+
* with status != "pending". `pending` means "seen but deferred by the
|
|
27
|
+
* maxPerSweep cap" — it is picked up on a later sweep. `failed` is NOT
|
|
28
|
+
* retried by default (avoids spawn loops); pass retryFailed to re-attempt.
|
|
29
|
+
* If the label is later removed from an issue, the entry persists, so the
|
|
30
|
+
* watcher still treats it as processed (it will not respawn).
|
|
31
|
+
*
|
|
32
|
+
* Write-ahead contract (the watcher relies on this ordering):
|
|
33
|
+
* 1. markProcessed(state, entry with status "spawned") + save → durable
|
|
34
|
+
* BEFORE the spawn command runs;
|
|
35
|
+
* 2. spawn;
|
|
36
|
+
* 3. markProcessed(state, entry with status "done"|"failed") + save.
|
|
37
|
+
* A crash between spawn and the final save leaves a "spawned" entry, which
|
|
38
|
+
* the next restart treats as processed — a double-spawn is impossible; a
|
|
39
|
+
* crash between the write-ahead save and the spawn leaves a "spawned" entry
|
|
40
|
+
* with no worktrees (safer than the alternative, and surfaced in the state
|
|
41
|
+
* file for manual inspection).
|
|
42
|
+
*
|
|
43
|
+
* Validation is deliberately LOOSE (plain fs + JSON, no schema library),
|
|
44
|
+
* matching src/orchestrator/state.ts style: missing/malformed files yield a
|
|
45
|
+
* fresh default state; entries that don't validate are dropped.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
import * as fs from "node:fs"
|
|
49
|
+
import * as fsp from "node:fs/promises"
|
|
50
|
+
import * as path from "node:path"
|
|
51
|
+
|
|
52
|
+
import type { WorktreeSpec } from "../orchestrator/split.js"
|
|
53
|
+
|
|
54
|
+
export type WatcherIssueStatus = "spawned" | "done" | "failed" | "skipped" | "pending"
|
|
55
|
+
|
|
56
|
+
/** One issue's processing record in the watcher state file. */
|
|
57
|
+
export interface WatcherIssueEntry {
|
|
58
|
+
number: number
|
|
59
|
+
title: string
|
|
60
|
+
/** The target label that triggered this entry (recorded for audit). */
|
|
61
|
+
label: string
|
|
62
|
+
/** Batch id (watcher-<date>); the batch maps 1:1 to a spawn invocation. */
|
|
63
|
+
batch?: string
|
|
64
|
+
/** Worktree groups produced by splitIssues([issue]). */
|
|
65
|
+
groups?: WorktreeSpec[]
|
|
66
|
+
spawnedAt?: string
|
|
67
|
+
status: WatcherIssueStatus
|
|
68
|
+
updatedAt: string
|
|
69
|
+
/** Error message when status === "failed". */
|
|
70
|
+
error?: string
|
|
71
|
+
/** Phase 4 pass-through: a QA round should follow this batch. */
|
|
72
|
+
qa?: boolean
|
|
73
|
+
/** Phase 4 pass-through: a deploy gate should follow this batch. */
|
|
74
|
+
deploy?: boolean
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The watcher state file document. */
|
|
78
|
+
export interface WatcherState {
|
|
79
|
+
processed: Record<string, WatcherIssueEntry>
|
|
80
|
+
updated?: string
|
|
81
|
+
lastSweep?: string
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function defaultWatcherState(): WatcherState {
|
|
85
|
+
return { processed: {}, updated: new Date().toISOString() }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Loosely validate a parsed state document; drops malformed entries. */
|
|
89
|
+
function sanitize(raw: unknown): WatcherState {
|
|
90
|
+
if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
|
|
91
|
+
const obj = raw as Record<string, unknown>
|
|
92
|
+
const processed: Record<string, WatcherIssueEntry> = {}
|
|
93
|
+
if (obj.processed !== null && typeof obj.processed === "object" && !Array.isArray(obj.processed)) {
|
|
94
|
+
for (const [key, value] of Object.entries(obj.processed)) {
|
|
95
|
+
if (value === null || typeof value !== "object") {
|
|
96
|
+
continue
|
|
97
|
+
}
|
|
98
|
+
const e = value as Record<string, unknown>
|
|
99
|
+
const number = Number(e.number)
|
|
100
|
+
if (!Number.isInteger(number) || number <= 0) {
|
|
101
|
+
continue
|
|
102
|
+
}
|
|
103
|
+
const entry: WatcherIssueEntry = {
|
|
104
|
+
number,
|
|
105
|
+
title: typeof e.title === "string" ? e.title : "",
|
|
106
|
+
label: typeof e.label === "string" ? e.label : "",
|
|
107
|
+
status: (typeof e.status === "string" ? e.status : "pending") as WatcherIssueStatus,
|
|
108
|
+
updatedAt: typeof e.updatedAt === "string" ? e.updatedAt : new Date().toISOString(),
|
|
109
|
+
}
|
|
110
|
+
if (typeof e.batch === "string") {
|
|
111
|
+
entry.batch = e.batch
|
|
112
|
+
}
|
|
113
|
+
if (Array.isArray(e.groups)) {
|
|
114
|
+
entry.groups = e.groups as WorktreeSpec[]
|
|
115
|
+
}
|
|
116
|
+
if (typeof e.spawnedAt === "string") {
|
|
117
|
+
entry.spawnedAt = e.spawnedAt
|
|
118
|
+
}
|
|
119
|
+
if (typeof e.error === "string") {
|
|
120
|
+
entry.error = e.error
|
|
121
|
+
}
|
|
122
|
+
if (typeof e.qa === "boolean") {
|
|
123
|
+
entry.qa = e.qa
|
|
124
|
+
}
|
|
125
|
+
if (typeof e.deploy === "boolean") {
|
|
126
|
+
entry.deploy = e.deploy
|
|
127
|
+
}
|
|
128
|
+
processed[key] = entry
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const state: WatcherState = { processed }
|
|
132
|
+
if (typeof obj.updated === "string") {
|
|
133
|
+
state.updated = obj.updated
|
|
134
|
+
}
|
|
135
|
+
if (typeof obj.lastSweep === "string") {
|
|
136
|
+
state.lastSweep = obj.lastSweep
|
|
137
|
+
}
|
|
138
|
+
return state
|
|
139
|
+
}
|
|
140
|
+
return defaultWatcherState()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Read + parse the watcher state file. Missing or malformed files yield a
|
|
145
|
+
* fresh default state (never throws for missing files). Issue #78: a parse
|
|
146
|
+
* error (e.g. a crash truncated the file mid-write, back when writes were
|
|
147
|
+
* non-atomic) is NOT fatal here either — unlike the orchestrator's state
|
|
148
|
+
* file, watcher state is a resumable idempotency cache, not the source of
|
|
149
|
+
* truth for in-flight work, so falling back to a fresh default lets the
|
|
150
|
+
* watcher keep running (worst case: a few issues get re-evaluated) instead
|
|
151
|
+
* of hard-failing on every subsequent poll.
|
|
152
|
+
*/
|
|
153
|
+
export async function loadWatcherState(statePath: string): Promise<WatcherState> {
|
|
154
|
+
let raw: string
|
|
155
|
+
try {
|
|
156
|
+
raw = await fsp.readFile(statePath, "utf-8")
|
|
157
|
+
} catch (error) {
|
|
158
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
159
|
+
return defaultWatcherState()
|
|
160
|
+
}
|
|
161
|
+
throw error
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
return sanitize(JSON.parse(raw))
|
|
165
|
+
} catch {
|
|
166
|
+
return defaultWatcherState()
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Synchronous variant; same semantics as loadWatcherState. */
|
|
171
|
+
export function loadWatcherStateSync(statePath: string): WatcherState {
|
|
172
|
+
let raw: string
|
|
173
|
+
try {
|
|
174
|
+
raw = fs.readFileSync(statePath, "utf-8")
|
|
175
|
+
} catch (error) {
|
|
176
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
177
|
+
return defaultWatcherState()
|
|
178
|
+
}
|
|
179
|
+
throw error
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
return sanitize(JSON.parse(raw))
|
|
183
|
+
} catch {
|
|
184
|
+
return defaultWatcherState()
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Unique sibling temp path for an atomic state write (mirrors
|
|
190
|
+
* src/orchestrator/state.ts's stateTmpPath). The pid + counter suffix keeps
|
|
191
|
+
* concurrent saves from ever sharing a temp file, which would race their
|
|
192
|
+
* renames.
|
|
193
|
+
*/
|
|
194
|
+
let watcherStateWriteCounter = 0
|
|
195
|
+
function watcherStateTmpPath(statePath: string): string {
|
|
196
|
+
return `${statePath}.tmp-${process.pid}-${++watcherStateWriteCounter}`
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Write the state file (pretty-printed + trailing newline), creating dirs.
|
|
201
|
+
* Issue #78: atomic temp+rename write, matching the orchestrator's
|
|
202
|
+
* saveState (src/orchestrator/state.ts) — a crash mid-write can no longer
|
|
203
|
+
* truncate .watcher-state.json (rename is atomic on the same filesystem).
|
|
204
|
+
*/
|
|
205
|
+
export async function saveWatcherState(statePath: string, state: WatcherState): Promise<void> {
|
|
206
|
+
const cleaned = sanitize(state)
|
|
207
|
+
cleaned.updated = new Date().toISOString()
|
|
208
|
+
await fsp.mkdir(path.dirname(statePath), { recursive: true })
|
|
209
|
+
const tmpPath = watcherStateTmpPath(statePath)
|
|
210
|
+
await fsp.writeFile(tmpPath, JSON.stringify(cleaned, null, 2) + "\n", "utf-8")
|
|
211
|
+
await fsp.rename(tmpPath, statePath)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Synchronous variant of saveWatcherState. */
|
|
215
|
+
export function saveWatcherStateSync(statePath: string, state: WatcherState): void {
|
|
216
|
+
const cleaned = sanitize(state)
|
|
217
|
+
cleaned.updated = new Date().toISOString()
|
|
218
|
+
fs.mkdirSync(path.dirname(statePath), { recursive: true })
|
|
219
|
+
const tmpPath = watcherStateTmpPath(statePath)
|
|
220
|
+
fs.writeFileSync(tmpPath, JSON.stringify(cleaned, null, 2) + "\n", "utf-8")
|
|
221
|
+
fs.renameSync(tmpPath, statePath)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Return a NEW state with the given entry upserted (immutable; the input
|
|
226
|
+
* state is not mutated). Stamps a fresh `updatedAt` on the entry.
|
|
227
|
+
*/
|
|
228
|
+
export function markProcessed(state: WatcherState, entry: WatcherIssueEntry): WatcherState {
|
|
229
|
+
const processed = { ...state.processed }
|
|
230
|
+
processed[String(entry.number)] = { ...entry, updatedAt: new Date().toISOString() }
|
|
231
|
+
return { ...state, processed, updated: new Date().toISOString() }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Is this issue considered already processed for the current sweep?
|
|
236
|
+
* - no entry -> false (new issue, spawn)
|
|
237
|
+
* - status pending -> false (deferred by the cap; pick up this sweep)
|
|
238
|
+
* - status failed -> false ONLY when retryFailed is set (--retry-failed)
|
|
239
|
+
* - everything else -> true (spawned/done/skipped — do not respawn,
|
|
240
|
+
* regardless of whether the label is still attached)
|
|
241
|
+
*/
|
|
242
|
+
export function isProcessed(state: WatcherState, number: number, options?: { retryFailed?: boolean }): boolean {
|
|
243
|
+
const entry = state.processed[String(number)]
|
|
244
|
+
if (!entry) {
|
|
245
|
+
return false
|
|
246
|
+
}
|
|
247
|
+
if (entry.status === "pending") {
|
|
248
|
+
return false
|
|
249
|
+
}
|
|
250
|
+
if (entry.status === "failed" && options?.retryFailed) {
|
|
251
|
+
return false
|
|
252
|
+
}
|
|
253
|
+
return true
|
|
254
|
+
}
|