supipowers 1.5.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -8
- package/bin/install.mjs +20 -5
- package/bin/install.ts +95 -0
- package/package.json +8 -4
- package/skills/context-mode/SKILL.md +17 -10
- package/skills/harness/SKILL.md +94 -0
- package/skills/ui-design/SKILL.md +63 -0
- package/skills/ui-design/sub-agent-templates/component-builder.md +29 -0
- package/skills/ui-design/sub-agent-templates/design-critic.md +46 -0
- package/skills/ui-design/sub-agent-templates/pencil/component-builder.md +29 -0
- package/skills/ui-design/sub-agent-templates/pencil/design-critic.md +42 -0
- package/skills/ui-design/sub-agent-templates/pencil/section-assembler.md +27 -0
- package/skills/ui-design/sub-agent-templates/section-assembler.md +27 -0
- package/skills/ultraplan-discover/SKILL.md +96 -0
- package/skills/ultraplan-intake/SKILL.md +89 -0
- package/skills/ultraplan-research/SKILL.md +129 -0
- package/skills/ultraplan-review/SKILL.md +86 -0
- package/skills/ultraplan-review-scope/SKILL.md +111 -0
- package/skills/ultraplan-review-structure/SKILL.md +120 -0
- package/skills/ultraplan-review-tdd/SKILL.md +142 -0
- package/skills/ultraplan-scout/SKILL.md +110 -0
- package/skills/ultraplan-synthesize/SKILL.md +124 -0
- package/src/{quality/ai-session.ts → ai/final-message.ts} +27 -0
- package/src/ai/schema-text.ts +129 -0
- package/src/ai/structured-output.ts +274 -0
- package/src/ai/template.ts +27 -0
- package/src/bootstrap.ts +63 -28
- package/src/commands/agents.ts +149 -45
- package/src/commands/ai-review.ts +251 -30
- package/src/commands/clear.ts +434 -0
- package/src/commands/commit.ts +1 -0
- package/src/commands/config.ts +242 -44
- package/src/commands/context.ts +55 -28
- package/src/commands/doctor.ts +234 -6
- package/src/commands/fix-pr.ts +306 -131
- package/src/commands/generate.ts +111 -21
- package/src/commands/memory.ts +192 -0
- package/src/commands/model-picker.ts +28 -21
- package/src/commands/model.ts +19 -9
- package/src/commands/optimize-context.ts +408 -29
- package/src/commands/plan.ts +2 -0
- package/src/commands/qa.ts +312 -137
- package/src/commands/release.ts +259 -76
- package/src/commands/review.ts +293 -59
- package/src/commands/status.ts +200 -13
- package/src/commands/supi.ts +3 -35
- package/src/commands/ui-design.ts +394 -0
- package/src/commands/ultraplan.ts +1518 -0
- package/src/commands/update.ts +86 -0
- package/src/config/defaults.ts +62 -0
- package/src/config/loader.ts +448 -60
- package/src/config/schema.ts +108 -2
- package/src/context/optimizer.ts +25 -33
- package/src/context/rule-renderer.ts +223 -0
- package/src/context/savings.ts +258 -0
- package/src/context/startup-check.ts +380 -0
- package/src/context/startup-optimizer.ts +355 -0
- package/src/context/tokenignore.ts +146 -0
- package/src/context-mode/cache-handle.ts +49 -0
- package/src/context-mode/cache-preview.ts +71 -0
- package/src/context-mode/cache-store.ts +738 -0
- package/src/context-mode/compressor.ts +131 -26
- package/src/context-mode/dedup.ts +108 -0
- package/src/context-mode/detector.ts +35 -4
- package/src/context-mode/event-extractor.ts +14 -12
- package/src/context-mode/event-store.ts +91 -36
- package/src/context-mode/hooks.ts +798 -56
- package/src/context-mode/knowledge/store.ts +255 -11
- package/src/context-mode/memory-store.ts +325 -0
- package/src/context-mode/metrics-recorder.ts +158 -0
- package/src/context-mode/metrics-store.ts +765 -0
- package/src/context-mode/model.ts +24 -0
- package/src/context-mode/processor-keys.ts +29 -0
- package/src/context-mode/processors/build.ts +66 -0
- package/src/context-mode/processors/docker.ts +57 -0
- package/src/context-mode/processors/git.ts +111 -0
- package/src/context-mode/processors/json.ts +112 -0
- package/src/context-mode/processors/k8s.ts +67 -0
- package/src/context-mode/processors/lint.ts +67 -0
- package/src/context-mode/processors/log.ts +86 -0
- package/src/context-mode/processors/registry.ts +116 -0
- package/src/context-mode/processors/test-runner.ts +102 -0
- package/src/context-mode/processors/types.ts +20 -0
- package/src/context-mode/repomap.ts +400 -0
- package/src/context-mode/routing.ts +97 -24
- package/src/context-mode/sandbox/runners.ts +5 -1
- package/src/context-mode/snapshot-builder.ts +106 -11
- package/src/context-mode/source-hash.ts +173 -0
- package/src/context-mode/tool-name.ts +11 -0
- package/src/context-mode/tools.ts +654 -22
- package/src/context-mode/web/fetcher.ts +31 -12
- package/src/debug/logger.ts +2 -1
- package/src/deps/registry.ts +1 -1
- package/src/discipline/failure-summarizer.ts +170 -0
- package/src/discipline/failure-taxonomy.ts +131 -0
- package/src/discipline/workflow-invariants.ts +125 -0
- package/src/discovery/index.ts +31 -0
- package/src/discovery/lsp.ts +87 -0
- package/src/discovery/rank.ts +144 -0
- package/src/discovery/sources.ts +89 -0
- package/src/discovery/workflow.ts +87 -0
- package/src/docs/contracts.ts +39 -0
- package/src/docs/drift.ts +117 -87
- package/src/fix-pr/assessment.ts +200 -0
- package/src/fix-pr/contracts.ts +47 -0
- package/src/fix-pr/fetch-comments.ts +80 -0
- package/src/fix-pr/prompt-builder.ts +58 -40
- package/src/fix-pr/scripts/exec.ts +34 -0
- package/src/fix-pr/scripts/trigger-review.ts +106 -0
- package/src/fix-pr/scripts/wait-and-check.ts +108 -0
- package/src/fix-pr/types.ts +4 -0
- package/src/git/branch-finish.ts +5 -0
- package/src/git/commit-contract.ts +83 -0
- package/src/git/commit.ts +121 -184
- package/src/git/status.ts +62 -8
- package/src/harness/anti_slop/architecture-parser.ts +210 -0
- package/src/harness/anti_slop/backend-factory.ts +30 -0
- package/src/harness/anti_slop/backend.ts +140 -0
- package/src/harness/anti_slop/desloppify-adapter.ts +319 -0
- package/src/harness/anti_slop/fallow-adapter.ts +305 -0
- package/src/harness/anti_slop/installer.ts +227 -0
- package/src/harness/anti_slop/queue.ts +216 -0
- package/src/harness/anti_slop/recommend.ts +84 -0
- package/src/harness/anti_slop/score.ts +180 -0
- package/src/harness/anti_slop/synthetic-edit-test.ts +128 -0
- package/src/harness/artifacts/agents-md.ts +88 -0
- package/src/harness/artifacts/checks-wiring.ts +57 -0
- package/src/harness/artifacts/docs-tree.ts +79 -0
- package/src/harness/artifacts/lint-configs.ts +136 -0
- package/src/harness/artifacts/review-agents.ts +67 -0
- package/src/harness/bare-entry.ts +108 -0
- package/src/harness/command.ts +1010 -0
- package/src/harness/default-agents/design.md +23 -0
- package/src/harness/default-agents/discover.md +18 -0
- package/src/harness/default-agents/implement.md +24 -0
- package/src/harness/default-agents/plan.md +19 -0
- package/src/harness/default-agents/research.md +21 -0
- package/src/harness/default-agents/validate.md +22 -0
- package/src/harness/gc/reporter.ts +28 -0
- package/src/harness/gc/runner.ts +136 -0
- package/src/harness/hooks/layer-context-inject.ts +155 -0
- package/src/harness/hooks/post-session-sweep.ts +130 -0
- package/src/harness/hooks/pre-edit-dupe-probe.ts +224 -0
- package/src/harness/hooks/register.ts +118 -0
- package/src/harness/model.ts +117 -0
- package/src/harness/pipeline.ts +348 -0
- package/src/harness/project-paths.ts +235 -0
- package/src/harness/stage-runner.ts +107 -0
- package/src/harness/stages/design.ts +386 -0
- package/src/harness/stages/discover.ts +454 -0
- package/src/harness/stages/implement.ts +162 -0
- package/src/harness/stages/plan.ts +335 -0
- package/src/harness/stages/research.ts +263 -0
- package/src/harness/stages/validate.ts +684 -0
- package/src/harness/storage.ts +467 -0
- package/src/harness/tools.ts +426 -0
- package/src/lsp/bridge.ts +56 -95
- package/src/lsp/capabilities.ts +108 -0
- package/src/lsp/contracts.ts +35 -0
- package/src/lsp/detector.ts +8 -12
- package/src/markdown-frontmatter.ts +68 -0
- package/src/mempalace/bridge.ts +129 -0
- package/src/mempalace/config.ts +75 -0
- package/src/mempalace/format.ts +163 -0
- package/src/mempalace/hooks.ts +370 -0
- package/src/mempalace/installer-helper.ts +194 -0
- package/src/mempalace/python/mempalace_bridge.py +440 -0
- package/src/mempalace/runtime.ts +565 -0
- package/src/mempalace/schema.ts +264 -0
- package/src/mempalace/session-summary.ts +198 -0
- package/src/mempalace/tool.ts +186 -0
- package/src/mempalace/uv.ts +256 -0
- package/src/migrate/runner.ts +354 -0
- package/src/planning/approval-flow.ts +206 -9
- package/src/planning/plan-writer-prompt.ts +4 -3
- package/src/planning/planning-ask-tool.ts +39 -0
- package/src/planning/render-markdown.ts +74 -0
- package/src/planning/spec.ts +42 -0
- package/src/planning/system-prompt.ts +11 -8
- package/src/planning/validate.ts +84 -0
- package/src/platform/omp.ts +15 -2
- package/src/platform/system-prompt.ts +37 -0
- package/src/platform/test-utils.ts +3 -0
- package/src/platform/types.ts +6 -1
- package/src/qa/config.ts +12 -6
- package/src/qa/detect-app-type.ts +13 -6
- package/src/qa/matrix.ts +12 -6
- package/src/qa/prompt-builder.ts +28 -30
- package/src/qa/scripts/dev-server-utils.ts +72 -0
- package/src/qa/scripts/run-e2e-tests.ts +226 -0
- package/src/qa/scripts/start-dev-server.ts +138 -0
- package/src/qa/scripts/stop-dev-server.ts +77 -0
- package/src/qa/session.ts +13 -7
- package/src/quality/ai-setup.ts +27 -25
- package/src/quality/contracts.ts +34 -0
- package/src/quality/gates/ai-review.ts +20 -58
- package/src/quality/gates/command.ts +249 -46
- package/src/quality/review-gates.ts +18 -2
- package/src/quality/runner.ts +63 -22
- package/src/quality/schemas.ts +37 -2
- package/src/quality/setup.ts +96 -16
- package/src/release/changelog.ts +1 -1
- package/src/release/channels/custom.ts +13 -3
- package/src/release/channels/types.ts +5 -0
- package/src/release/contracts.ts +90 -0
- package/src/release/executor.ts +122 -45
- package/src/release/prompt.ts +18 -2
- package/src/release/targets.ts +86 -0
- package/src/release/version.ts +96 -71
- package/src/review/agent-loader.ts +298 -127
- package/src/review/fixer.ts +10 -6
- package/src/review/multi-agent-runner.ts +115 -14
- package/src/review/output.ts +12 -139
- package/src/review/runner.ts +12 -6
- package/src/review/scope.ts +144 -24
- package/src/review/types.ts +11 -20
- package/src/review/validator.ts +12 -6
- package/src/storage/fix-pr-sessions.ts +21 -14
- package/src/storage/plans.ts +14 -5
- package/src/storage/qa-sessions.ts +25 -19
- package/src/storage/reliability-metrics.ts +180 -0
- package/src/storage/reports.ts +8 -7
- package/src/storage/review-sessions.ts +55 -20
- package/src/tool-catalog/active-tool-controller.ts +164 -0
- package/src/tool-catalog/active-tool-planner.ts +212 -0
- package/src/tool-catalog/tool-groups.ts +102 -0
- package/src/types.ts +1401 -5
- package/src/ui-design/backend-adapter.ts +78 -0
- package/src/ui-design/backends/local-html.ts +82 -0
- package/src/ui-design/backends/pencil-mcp.ts +111 -0
- package/src/ui-design/components-scanner.ts +124 -0
- package/src/ui-design/config.ts +55 -0
- package/src/ui-design/pen-scanner.ts +95 -0
- package/src/ui-design/pen-selector.ts +72 -0
- package/src/ui-design/prompt-builder.ts +73 -0
- package/src/ui-design/scanner.ts +136 -0
- package/src/ui-design/session.ts +974 -0
- package/src/ui-design/system-prompt.ts +312 -0
- package/src/ui-design/tokens-scanner.ts +181 -0
- package/src/ui-design/types.ts +96 -0
- package/src/ultraplan/agent-catalog.ts +522 -0
- package/src/ultraplan/authoring/agent-catalog.ts +310 -0
- package/src/ultraplan/authoring/authoring-tools.ts +552 -0
- package/src/ultraplan/authoring/command-handlers.ts +339 -0
- package/src/ultraplan/authoring/markdown.ts +510 -0
- package/src/ultraplan/authoring/model.ts +162 -0
- package/src/ultraplan/authoring/pipeline.ts +319 -0
- package/src/ultraplan/authoring/stage-runner.ts +141 -0
- package/src/ultraplan/authoring/stages/approve.ts +249 -0
- package/src/ultraplan/authoring/stages/discover.ts +289 -0
- package/src/ultraplan/authoring/stages/intake.ts +203 -0
- package/src/ultraplan/authoring/stages/research.ts +399 -0
- package/src/ultraplan/authoring/stages/review.ts +333 -0
- package/src/ultraplan/authoring/stages/scout.ts +188 -0
- package/src/ultraplan/authoring/stages/synthesize.ts +348 -0
- package/src/ultraplan/authoring/storage.ts +594 -0
- package/src/ultraplan/authoring/synth-gate.ts +165 -0
- package/src/ultraplan/authoring-draft.ts +653 -0
- package/src/ultraplan/authoring-persist.ts +180 -0
- package/src/ultraplan/authoring-tool.ts +608 -0
- package/src/ultraplan/authoring-wizard.ts +587 -0
- package/src/ultraplan/batch/merge.ts +98 -0
- package/src/ultraplan/batch/planner.ts +150 -0
- package/src/ultraplan/batch/presenter.ts +97 -0
- package/src/ultraplan/batch/storage.ts +420 -0
- package/src/ultraplan/batch/supervisor.ts +317 -0
- package/src/ultraplan/batch/worker.ts +26 -0
- package/src/ultraplan/batch/worktree.ts +110 -0
- package/src/ultraplan/contracts.ts +1593 -0
- package/src/ultraplan/default-agents/authoring/discoverer.md +12 -0
- package/src/ultraplan/default-agents/authoring/intake.md +12 -0
- package/src/ultraplan/default-agents/authoring/planner.md +12 -0
- package/src/ultraplan/default-agents/authoring/researcher.md +12 -0
- package/src/ultraplan/default-agents/authoring/scope-checker.md +12 -0
- package/src/ultraplan/default-agents/authoring/scout.md +12 -0
- package/src/ultraplan/default-agents/authoring/structure-checker.md +12 -0
- package/src/ultraplan/default-agents/authoring/tdd-checker.md +12 -0
- package/src/ultraplan/default-agents/backend-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/backend-executor.md +10 -0
- package/src/ultraplan/default-agents/backend-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/backend-tester.md +10 -0
- package/src/ultraplan/default-agents/frontend-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/frontend-executor.md +10 -0
- package/src/ultraplan/default-agents/frontend-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/frontend-tester.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-executor.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-tester.md +10 -0
- package/src/ultraplan/execution/contract.ts +71 -0
- package/src/ultraplan/execution/policy.ts +217 -0
- package/src/ultraplan/execution/runtime-tools.ts +107 -0
- package/src/ultraplan/execution/session-runner.ts +281 -0
- package/src/ultraplan/next-router.ts +85 -0
- package/src/ultraplan/presenter.ts +359 -0
- package/src/ultraplan/project-paths.ts +342 -0
- package/src/ultraplan/runtime/active-execution.ts +72 -0
- package/src/ultraplan/runtime/apply-mutation.ts +416 -0
- package/src/ultraplan/runtime/blockers.ts +243 -0
- package/src/ultraplan/runtime/hook-bridge.ts +486 -0
- package/src/ultraplan/runtime/launch-context.ts +207 -0
- package/src/ultraplan/runtime/migration.ts +524 -0
- package/src/ultraplan/runtime/normalize.ts +281 -0
- package/src/ultraplan/runtime/proof.ts +260 -0
- package/src/ultraplan/runtime/reducer.ts +416 -0
- package/src/ultraplan/runtime/repair.ts +251 -0
- package/src/ultraplan/runtime/tracker-storage.ts +368 -0
- package/src/ultraplan/session-selection.ts +291 -0
- package/src/ultraplan/storage.ts +374 -0
- package/src/utils/editor.ts +38 -0
- package/src/utils/executable.ts +80 -0
- package/src/utils/paths.ts +1 -20
- package/src/utils/shell.ts +31 -0
- package/src/visual/companion.ts +2 -1
- package/src/visual/scripts/frame-template.html +60 -0
- package/src/visual/scripts/index.js +59 -13
- package/src/visual/scripts/package.json +3 -0
- package/src/visual/start-server.ts +2 -1
- package/src/workspace/git-scope.ts +64 -0
- package/src/workspace/locks.ts +23 -0
- package/src/workspace/package-manager.ts +117 -0
- package/src/workspace/path-mapping.ts +75 -0
- package/src/workspace/project-slug.ts +92 -0
- package/src/workspace/repo-root.ts +137 -0
- package/src/workspace/selector.ts +115 -0
- package/src/workspace/state-paths.ts +118 -0
- package/src/workspace/targets.ts +313 -0
- package/src/fix-pr/scripts/diff-comments.sh +0 -33
- package/src/fix-pr/scripts/fetch-pr-comments.sh +0 -25
- package/src/fix-pr/scripts/trigger-review.sh +0 -36
- package/src/fix-pr/scripts/wait-and-check.sh +0 -37
- package/src/qa/scripts/detect-app-type.sh +0 -68
- package/src/qa/scripts/discover-routes.sh +0 -143
- package/src/qa/scripts/run-e2e-tests.sh +0 -131
- package/src/qa/scripts/start-dev-server.sh +0 -46
- package/src/qa/scripts/stop-dev-server.sh +0 -36
- package/src/review/prompts/fix-output-schema.md +0 -18
- package/src/review/prompts/review-output-schema.md +0 -38
- package/src/review/template.ts +0 -15
- /package/src/{review → ai}/prompts/invalid-output-retry.md +0 -0
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage helpers for the multi-stage authoring pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Authoring artifacts live under `<session>/authoring/` and never touch the runtime tracker
|
|
5
|
+
* or the canonical `authored.json` until the APPROVE stage promotes them. This module is a
|
|
6
|
+
* thin wrapper over `node:fs` that:
|
|
7
|
+
* - validates each artifact against its TypeBox schema before writing,
|
|
8
|
+
* - writes JSON atomically (temp + rename),
|
|
9
|
+
* - appends to `pipeline-log.jsonl` line-by-line (no rewriting the whole file),
|
|
10
|
+
* - exposes load helpers that distinguish "missing" from "invalid" cleanly.
|
|
11
|
+
*
|
|
12
|
+
* Filesystem failures are returned as structured `UltraPlanStorageResult` values so callers
|
|
13
|
+
* never need to wrap calls in try/catch. The shape mirrors `src/ultraplan/storage.ts` and the
|
|
14
|
+
* runtime `tracker-storage.ts` deliberately so the consumer ergonomics are identical.
|
|
15
|
+
*/
|
|
16
|
+
import * as fs from "node:fs";
|
|
17
|
+
import * as path from "node:path";
|
|
18
|
+
|
|
19
|
+
import type { PlatformPaths } from "../../platform/types.js";
|
|
20
|
+
import type {
|
|
21
|
+
UltraPlanAuthoringFindingsArtifact,
|
|
22
|
+
UltraPlanAuthoringPipelineEvent,
|
|
23
|
+
UltraPlanAuthoringState,
|
|
24
|
+
UltraPlanStackId,
|
|
25
|
+
UltraPlanStorageError,
|
|
26
|
+
UltraPlanStorageResult,
|
|
27
|
+
} from "../../types.js";
|
|
28
|
+
import {
|
|
29
|
+
validateUltraPlanAuthoringFindingsArtifact,
|
|
30
|
+
validateUltraPlanAuthoringPipelineEvent,
|
|
31
|
+
validateUltraPlanAuthoringState,
|
|
32
|
+
} from "../contracts.js";
|
|
33
|
+
import {
|
|
34
|
+
getUltraplanAuthoringDecisionsPath,
|
|
35
|
+
getUltraplanAuthoringDeferredIdeasPath,
|
|
36
|
+
getUltraplanAuthoringDir,
|
|
37
|
+
getUltraplanAuthoringDiscussPath,
|
|
38
|
+
getUltraplanAuthoringDraftAuthoredJsonPath,
|
|
39
|
+
getUltraplanAuthoringDraftAuthoredMarkdownPath,
|
|
40
|
+
getUltraplanAuthoringDraftFindingsPath,
|
|
41
|
+
getUltraplanAuthoringDraftIterationDir,
|
|
42
|
+
getUltraplanAuthoringDraftPlannerJsonPath,
|
|
43
|
+
getUltraplanAuthoringIntakePath,
|
|
44
|
+
getUltraplanAuthoringPipelineLogPath,
|
|
45
|
+
getUltraplanAuthoringResearchStackPath,
|
|
46
|
+
getUltraplanAuthoringResearchSummaryPath,
|
|
47
|
+
getUltraplanAuthoringScoutPath,
|
|
48
|
+
} from "../project-paths.js";
|
|
49
|
+
import { loadUltraPlanManifest, saveUltraPlanManifest } from "../storage.js";
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Result helpers (kept private; matches the shape used elsewhere in storage.ts).
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
function success<T>(value: T): UltraPlanStorageResult<T> {
|
|
56
|
+
return { ok: true, value };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function failure(
|
|
60
|
+
pathname: string,
|
|
61
|
+
kind: UltraPlanStorageError["kind"],
|
|
62
|
+
message: string,
|
|
63
|
+
details?: string[],
|
|
64
|
+
): UltraPlanStorageResult<never> {
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
error: {
|
|
68
|
+
kind,
|
|
69
|
+
path: pathname,
|
|
70
|
+
message,
|
|
71
|
+
...(details ? { details } : {}),
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function ensureDir(filePath: string): void {
|
|
77
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function ensureDirExists(dirPath: string): void {
|
|
81
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Atomic JSON write: serialize → write to a per-process temp file → rename. The rename is
|
|
86
|
+
* atomic on the same filesystem, so concurrent readers either see the previous file or the
|
|
87
|
+
* full new file, never a half-written one.
|
|
88
|
+
*/
|
|
89
|
+
function writeJsonAtomic(filePath: string, payload: unknown): UltraPlanStorageResult<string> {
|
|
90
|
+
try {
|
|
91
|
+
ensureDir(filePath);
|
|
92
|
+
const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
93
|
+
fs.writeFileSync(tmpPath, `${JSON.stringify(payload, null, 2)}\n`);
|
|
94
|
+
fs.renameSync(tmpPath, filePath);
|
|
95
|
+
return success(filePath);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
return failure(
|
|
98
|
+
filePath,
|
|
99
|
+
"io",
|
|
100
|
+
error instanceof Error ? error.message : `Unable to write ${filePath}`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Atomic text write (markdown, decisions, deferred ideas). Same temp+rename strategy.
|
|
107
|
+
*/
|
|
108
|
+
function writeTextAtomic(filePath: string, content: string): UltraPlanStorageResult<string> {
|
|
109
|
+
try {
|
|
110
|
+
ensureDir(filePath);
|
|
111
|
+
const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
112
|
+
fs.writeFileSync(tmpPath, content.endsWith("\n") ? content : `${content}\n`);
|
|
113
|
+
fs.renameSync(tmpPath, filePath);
|
|
114
|
+
return success(filePath);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
return failure(
|
|
117
|
+
filePath,
|
|
118
|
+
"io",
|
|
119
|
+
error instanceof Error ? error.message : `Unable to write ${filePath}`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function readJsonFile(filePath: string): UltraPlanStorageResult<unknown> {
|
|
125
|
+
if (!fs.existsSync(filePath)) {
|
|
126
|
+
return failure(filePath, "missing", `Artifact not found: ${filePath}`);
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
return success(JSON.parse(fs.readFileSync(filePath, "utf8")));
|
|
130
|
+
} catch (error) {
|
|
131
|
+
return failure(
|
|
132
|
+
filePath,
|
|
133
|
+
"invalid-json",
|
|
134
|
+
error instanceof Error ? error.message : `Invalid JSON in ${filePath}`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function readTextFile(filePath: string): UltraPlanStorageResult<string> {
|
|
140
|
+
if (!fs.existsSync(filePath)) {
|
|
141
|
+
return failure(filePath, "missing", `Artifact not found: ${filePath}`);
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
return success(fs.readFileSync(filePath, "utf8"));
|
|
145
|
+
} catch (error) {
|
|
146
|
+
return failure(
|
|
147
|
+
filePath,
|
|
148
|
+
"io",
|
|
149
|
+
error instanceof Error ? error.message : `Unable to read ${filePath}`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// Authoring state (the manifest's `authoring` block).
|
|
156
|
+
//
|
|
157
|
+
// The state lives inside the manifest, not in a sibling file: the manifest is already the
|
|
158
|
+
// canonical truth for "what session is this and what state is it in," and the authoring
|
|
159
|
+
// block is just an extension of that. We expose load/save helpers that go through the
|
|
160
|
+
// manifest so callers don't need to know the embedding.
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Load just the authoring block from the manifest. Returns `null` (wrapped in success) when
|
|
165
|
+
* the manifest exists but has no authoring block — this is the common case for legacy
|
|
166
|
+
* single-shot sessions and for sessions that have been promoted to `state: "ready"`.
|
|
167
|
+
*/
|
|
168
|
+
export function loadAuthoringState(
|
|
169
|
+
paths: PlatformPaths,
|
|
170
|
+
cwd: string,
|
|
171
|
+
sessionId: string,
|
|
172
|
+
): UltraPlanStorageResult<UltraPlanAuthoringState | null> {
|
|
173
|
+
const manifestResult = loadUltraPlanManifest(paths, cwd, sessionId);
|
|
174
|
+
if (!manifestResult.ok) return manifestResult;
|
|
175
|
+
return success(manifestResult.value.authoring ?? null);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Persist the authoring state by overwriting the manifest's `authoring` field. The full
|
|
180
|
+
* manifest is round-tripped through schema validation before being written, so this also
|
|
181
|
+
* implicitly validates the authoring block against `UltraPlanAuthoringStateSchema`.
|
|
182
|
+
*
|
|
183
|
+
* Pre-validates the authoring block independently for a clearer error path — callers see
|
|
184
|
+
* the authoring-specific errors instead of a manifest-shaped error message.
|
|
185
|
+
*/
|
|
186
|
+
export function saveAuthoringState(
|
|
187
|
+
paths: PlatformPaths,
|
|
188
|
+
cwd: string,
|
|
189
|
+
sessionId: string,
|
|
190
|
+
authoring: UltraPlanAuthoringState,
|
|
191
|
+
): UltraPlanStorageResult<string> {
|
|
192
|
+
const validation = validateUltraPlanAuthoringState(authoring);
|
|
193
|
+
if (!validation.ok) {
|
|
194
|
+
return failure(
|
|
195
|
+
"authoring-state",
|
|
196
|
+
"validation-error",
|
|
197
|
+
"Authoring state failed schema validation",
|
|
198
|
+
validation.errors,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const manifestResult = loadUltraPlanManifest(paths, cwd, sessionId);
|
|
203
|
+
if (!manifestResult.ok) return manifestResult;
|
|
204
|
+
|
|
205
|
+
const next = { ...manifestResult.value, authoring: validation.value, updatedAt: new Date().toISOString() };
|
|
206
|
+
const saved = saveUltraPlanManifest(paths, cwd, sessionId, next);
|
|
207
|
+
return saved;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Clear the authoring block. Used by the APPROVE stage after the canonical artifacts are
|
|
212
|
+
* promoted. Equivalent to `saveAuthoringState` with `undefined`, but the underlying manifest
|
|
213
|
+
* schema makes the field optional so we explicitly drop it.
|
|
214
|
+
*/
|
|
215
|
+
export function clearAuthoringState(
|
|
216
|
+
paths: PlatformPaths,
|
|
217
|
+
cwd: string,
|
|
218
|
+
sessionId: string,
|
|
219
|
+
): UltraPlanStorageResult<string> {
|
|
220
|
+
const manifestResult = loadUltraPlanManifest(paths, cwd, sessionId);
|
|
221
|
+
if (!manifestResult.ok) return manifestResult;
|
|
222
|
+
const { authoring: _drop, ...rest } = manifestResult.value;
|
|
223
|
+
void _drop;
|
|
224
|
+
const next = { ...rest, updatedAt: new Date().toISOString() };
|
|
225
|
+
return saveUltraPlanManifest(paths, cwd, sessionId, next);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
// Stage artifacts (intake, scout, discuss, deferred-ideas, research/<stack>.md, drafts).
|
|
230
|
+
// JSON artifacts go through schema validation; markdown artifacts are stored as opaque text.
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
export function saveIntakeArtifact(
|
|
234
|
+
paths: PlatformPaths,
|
|
235
|
+
cwd: string,
|
|
236
|
+
sessionId: string,
|
|
237
|
+
artifact: unknown,
|
|
238
|
+
): UltraPlanStorageResult<string> {
|
|
239
|
+
// The intake schema is owned by the intake stage runner (Phase 3) — at the substrate level
|
|
240
|
+
// we accept any JSON that round-trips, so legacy callers and tests can drop any object.
|
|
241
|
+
return writeJsonAtomic(getUltraplanAuthoringIntakePath(paths, cwd, sessionId), artifact);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function loadIntakeArtifact(
|
|
245
|
+
paths: PlatformPaths,
|
|
246
|
+
cwd: string,
|
|
247
|
+
sessionId: string,
|
|
248
|
+
): UltraPlanStorageResult<unknown> {
|
|
249
|
+
return readJsonFile(getUltraplanAuthoringIntakePath(paths, cwd, sessionId));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function saveScoutArtifact(
|
|
253
|
+
paths: PlatformPaths,
|
|
254
|
+
cwd: string,
|
|
255
|
+
sessionId: string,
|
|
256
|
+
artifact: unknown,
|
|
257
|
+
): UltraPlanStorageResult<string> {
|
|
258
|
+
return writeJsonAtomic(getUltraplanAuthoringScoutPath(paths, cwd, sessionId), artifact);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function loadScoutArtifact(
|
|
262
|
+
paths: PlatformPaths,
|
|
263
|
+
cwd: string,
|
|
264
|
+
sessionId: string,
|
|
265
|
+
): UltraPlanStorageResult<unknown> {
|
|
266
|
+
return readJsonFile(getUltraplanAuthoringScoutPath(paths, cwd, sessionId));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function saveDiscussArtifact(
|
|
270
|
+
paths: PlatformPaths,
|
|
271
|
+
cwd: string,
|
|
272
|
+
sessionId: string,
|
|
273
|
+
markdown: string,
|
|
274
|
+
): UltraPlanStorageResult<string> {
|
|
275
|
+
return writeTextAtomic(getUltraplanAuthoringDiscussPath(paths, cwd, sessionId), markdown);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function loadDiscussArtifact(
|
|
279
|
+
paths: PlatformPaths,
|
|
280
|
+
cwd: string,
|
|
281
|
+
sessionId: string,
|
|
282
|
+
): UltraPlanStorageResult<string> {
|
|
283
|
+
return readTextFile(getUltraplanAuthoringDiscussPath(paths, cwd, sessionId));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function saveDeferredIdeas(
|
|
287
|
+
paths: PlatformPaths,
|
|
288
|
+
cwd: string,
|
|
289
|
+
sessionId: string,
|
|
290
|
+
markdown: string,
|
|
291
|
+
): UltraPlanStorageResult<string> {
|
|
292
|
+
return writeTextAtomic(getUltraplanAuthoringDeferredIdeasPath(paths, cwd, sessionId), markdown);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function loadDeferredIdeas(
|
|
296
|
+
paths: PlatformPaths,
|
|
297
|
+
cwd: string,
|
|
298
|
+
sessionId: string,
|
|
299
|
+
): UltraPlanStorageResult<string> {
|
|
300
|
+
return readTextFile(getUltraplanAuthoringDeferredIdeasPath(paths, cwd, sessionId));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** Append a single decision JSONL line. Caller is responsible for the schema/shape. */
|
|
304
|
+
export function appendDecisionRecord(
|
|
305
|
+
paths: PlatformPaths,
|
|
306
|
+
cwd: string,
|
|
307
|
+
sessionId: string,
|
|
308
|
+
decision: Record<string, unknown>,
|
|
309
|
+
): UltraPlanStorageResult<string> {
|
|
310
|
+
const filePath = getUltraplanAuthoringDecisionsPath(paths, cwd, sessionId);
|
|
311
|
+
try {
|
|
312
|
+
ensureDir(filePath);
|
|
313
|
+
fs.appendFileSync(filePath, `${JSON.stringify(decision)}\n`);
|
|
314
|
+
return success(filePath);
|
|
315
|
+
} catch (error) {
|
|
316
|
+
return failure(
|
|
317
|
+
filePath,
|
|
318
|
+
"io",
|
|
319
|
+
error instanceof Error ? error.message : `Unable to append decision to ${filePath}`,
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function saveResearchStackArtifact(
|
|
325
|
+
paths: PlatformPaths,
|
|
326
|
+
cwd: string,
|
|
327
|
+
sessionId: string,
|
|
328
|
+
stack: UltraPlanStackId,
|
|
329
|
+
markdown: string,
|
|
330
|
+
): UltraPlanStorageResult<string> {
|
|
331
|
+
return writeTextAtomic(getUltraplanAuthoringResearchStackPath(paths, cwd, sessionId, stack), markdown);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function loadResearchStackArtifact(
|
|
335
|
+
paths: PlatformPaths,
|
|
336
|
+
cwd: string,
|
|
337
|
+
sessionId: string,
|
|
338
|
+
stack: UltraPlanStackId,
|
|
339
|
+
): UltraPlanStorageResult<string> {
|
|
340
|
+
return readTextFile(getUltraplanAuthoringResearchStackPath(paths, cwd, sessionId, stack));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Remove a per-stack research artifact. Used when a stack flips from `applicable` to
|
|
345
|
+
* `not-applicable` mid-pipeline (the skip-stack invariant in Phase 5). Missing files are a
|
|
346
|
+
* no-op, not an error.
|
|
347
|
+
*/
|
|
348
|
+
export function deleteResearchStackArtifact(
|
|
349
|
+
paths: PlatformPaths,
|
|
350
|
+
cwd: string,
|
|
351
|
+
sessionId: string,
|
|
352
|
+
stack: UltraPlanStackId,
|
|
353
|
+
): UltraPlanStorageResult<string> {
|
|
354
|
+
const filePath = getUltraplanAuthoringResearchStackPath(paths, cwd, sessionId, stack);
|
|
355
|
+
try {
|
|
356
|
+
if (fs.existsSync(filePath)) {
|
|
357
|
+
fs.unlinkSync(filePath);
|
|
358
|
+
}
|
|
359
|
+
return success(filePath);
|
|
360
|
+
} catch (error) {
|
|
361
|
+
return failure(
|
|
362
|
+
filePath,
|
|
363
|
+
"io",
|
|
364
|
+
error instanceof Error ? error.message : `Unable to delete ${filePath}`,
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export function saveResearchSummary(
|
|
370
|
+
paths: PlatformPaths,
|
|
371
|
+
cwd: string,
|
|
372
|
+
sessionId: string,
|
|
373
|
+
markdown: string,
|
|
374
|
+
): UltraPlanStorageResult<string> {
|
|
375
|
+
return writeTextAtomic(getUltraplanAuthoringResearchSummaryPath(paths, cwd, sessionId), markdown);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export function loadResearchSummary(
|
|
379
|
+
paths: PlatformPaths,
|
|
380
|
+
cwd: string,
|
|
381
|
+
sessionId: string,
|
|
382
|
+
): UltraPlanStorageResult<string> {
|
|
383
|
+
return readTextFile(getUltraplanAuthoringResearchSummaryPath(paths, cwd, sessionId));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ---------------------------------------------------------------------------
|
|
387
|
+
// Drafts: per-iteration directories under `drafts/iteration-N/`.
|
|
388
|
+
// ---------------------------------------------------------------------------
|
|
389
|
+
|
|
390
|
+
export function ensureDraftIterationDir(
|
|
391
|
+
paths: PlatformPaths,
|
|
392
|
+
cwd: string,
|
|
393
|
+
sessionId: string,
|
|
394
|
+
iteration: number,
|
|
395
|
+
): string {
|
|
396
|
+
const dir = getUltraplanAuthoringDraftIterationDir(paths, cwd, sessionId, iteration);
|
|
397
|
+
ensureDirExists(dir);
|
|
398
|
+
return dir;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function saveDraftAuthoredJson(
|
|
402
|
+
paths: PlatformPaths,
|
|
403
|
+
cwd: string,
|
|
404
|
+
sessionId: string,
|
|
405
|
+
iteration: number,
|
|
406
|
+
artifact: unknown,
|
|
407
|
+
): UltraPlanStorageResult<string> {
|
|
408
|
+
return writeJsonAtomic(
|
|
409
|
+
getUltraplanAuthoringDraftAuthoredJsonPath(paths, cwd, sessionId, iteration),
|
|
410
|
+
artifact,
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export function loadDraftAuthoredJson(
|
|
415
|
+
paths: PlatformPaths,
|
|
416
|
+
cwd: string,
|
|
417
|
+
sessionId: string,
|
|
418
|
+
iteration: number,
|
|
419
|
+
): UltraPlanStorageResult<unknown> {
|
|
420
|
+
return readJsonFile(getUltraplanAuthoringDraftAuthoredJsonPath(paths, cwd, sessionId, iteration));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export function saveDraftAuthoredMarkdown(
|
|
424
|
+
paths: PlatformPaths,
|
|
425
|
+
cwd: string,
|
|
426
|
+
sessionId: string,
|
|
427
|
+
iteration: number,
|
|
428
|
+
markdown: string,
|
|
429
|
+
): UltraPlanStorageResult<string> {
|
|
430
|
+
return writeTextAtomic(
|
|
431
|
+
getUltraplanAuthoringDraftAuthoredMarkdownPath(paths, cwd, sessionId, iteration),
|
|
432
|
+
markdown,
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export function loadDraftAuthoredMarkdown(
|
|
437
|
+
paths: PlatformPaths,
|
|
438
|
+
cwd: string,
|
|
439
|
+
sessionId: string,
|
|
440
|
+
iteration: number,
|
|
441
|
+
): UltraPlanStorageResult<string> {
|
|
442
|
+
return readTextFile(getUltraplanAuthoringDraftAuthoredMarkdownPath(paths, cwd, sessionId, iteration));
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Snapshot the planner's emitted draft before any user editing happens. Stored alongside the
|
|
447
|
+
* editable draft so forensics can compare what the planner wrote vs. what the user shipped.
|
|
448
|
+
*/
|
|
449
|
+
export function saveDraftPlannerJson(
|
|
450
|
+
paths: PlatformPaths,
|
|
451
|
+
cwd: string,
|
|
452
|
+
sessionId: string,
|
|
453
|
+
iteration: number,
|
|
454
|
+
artifact: unknown,
|
|
455
|
+
): UltraPlanStorageResult<string> {
|
|
456
|
+
return writeJsonAtomic(
|
|
457
|
+
getUltraplanAuthoringDraftPlannerJsonPath(paths, cwd, sessionId, iteration),
|
|
458
|
+
artifact,
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export function saveFindingsArtifact(
|
|
463
|
+
paths: PlatformPaths,
|
|
464
|
+
cwd: string,
|
|
465
|
+
sessionId: string,
|
|
466
|
+
iteration: number,
|
|
467
|
+
findings: UltraPlanAuthoringFindingsArtifact,
|
|
468
|
+
): UltraPlanStorageResult<string> {
|
|
469
|
+
const validation = validateUltraPlanAuthoringFindingsArtifact(findings);
|
|
470
|
+
if (!validation.ok) {
|
|
471
|
+
return failure(
|
|
472
|
+
"findings-artifact",
|
|
473
|
+
"validation-error",
|
|
474
|
+
"Findings artifact failed schema validation",
|
|
475
|
+
validation.errors,
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
return writeJsonAtomic(
|
|
479
|
+
getUltraplanAuthoringDraftFindingsPath(paths, cwd, sessionId, iteration),
|
|
480
|
+
validation.value,
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export function loadFindingsArtifact(
|
|
485
|
+
paths: PlatformPaths,
|
|
486
|
+
cwd: string,
|
|
487
|
+
sessionId: string,
|
|
488
|
+
iteration: number,
|
|
489
|
+
): UltraPlanStorageResult<UltraPlanAuthoringFindingsArtifact> {
|
|
490
|
+
const filePath = getUltraplanAuthoringDraftFindingsPath(paths, cwd, sessionId, iteration);
|
|
491
|
+
const parsed = readJsonFile(filePath);
|
|
492
|
+
if (!parsed.ok) return parsed;
|
|
493
|
+
const validation = validateUltraPlanAuthoringFindingsArtifact(parsed.value);
|
|
494
|
+
if (!validation.ok) {
|
|
495
|
+
return failure(filePath, "validation-error", `Findings failed schema validation: ${filePath}`, validation.errors);
|
|
496
|
+
}
|
|
497
|
+
return success(validation.value);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// ---------------------------------------------------------------------------
|
|
501
|
+
// Pipeline log (append-only JSONL).
|
|
502
|
+
// ---------------------------------------------------------------------------
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Append a single event to `pipeline-log.jsonl`. The event is validated against
|
|
506
|
+
* `UltraPlanAuthoringPipelineEventSchema` so malformed entries never reach disk.
|
|
507
|
+
*
|
|
508
|
+
* `fs.appendFileSync` is sufficient here because line-oriented writes < PIPE_BUF are atomic
|
|
509
|
+
* on POSIX, and Windows serializes `appendFileSync` calls within a process. Concurrent
|
|
510
|
+
* authoring runs across different session IDs write to different files, so contention is
|
|
511
|
+
* not a concern.
|
|
512
|
+
*/
|
|
513
|
+
export function appendPipelineLog(
|
|
514
|
+
paths: PlatformPaths,
|
|
515
|
+
cwd: string,
|
|
516
|
+
sessionId: string,
|
|
517
|
+
event: UltraPlanAuthoringPipelineEvent,
|
|
518
|
+
): UltraPlanStorageResult<string> {
|
|
519
|
+
const validation = validateUltraPlanAuthoringPipelineEvent(event);
|
|
520
|
+
if (!validation.ok) {
|
|
521
|
+
return failure(
|
|
522
|
+
"pipeline-log-event",
|
|
523
|
+
"validation-error",
|
|
524
|
+
"Pipeline event failed schema validation",
|
|
525
|
+
validation.errors,
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const filePath = getUltraplanAuthoringPipelineLogPath(paths, cwd, sessionId);
|
|
530
|
+
try {
|
|
531
|
+
ensureDir(filePath);
|
|
532
|
+
fs.appendFileSync(filePath, `${JSON.stringify(validation.value)}\n`);
|
|
533
|
+
return success(filePath);
|
|
534
|
+
} catch (error) {
|
|
535
|
+
return failure(
|
|
536
|
+
filePath,
|
|
537
|
+
"io",
|
|
538
|
+
error instanceof Error ? error.message : `Unable to append pipeline log entry to ${filePath}`,
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Read the full pipeline log into memory. Returns an empty array on missing file (this is
|
|
545
|
+
* the common state on a brand-new authoring session). Lines that fail schema validation are
|
|
546
|
+
* skipped silently — a corrupt log line should not block the picker or status presenter.
|
|
547
|
+
*/
|
|
548
|
+
export function readPipelineLog(
|
|
549
|
+
paths: PlatformPaths,
|
|
550
|
+
cwd: string,
|
|
551
|
+
sessionId: string,
|
|
552
|
+
): UltraPlanStorageResult<UltraPlanAuthoringPipelineEvent[]> {
|
|
553
|
+
const filePath = getUltraplanAuthoringPipelineLogPath(paths, cwd, sessionId);
|
|
554
|
+
if (!fs.existsSync(filePath)) {
|
|
555
|
+
return success([]);
|
|
556
|
+
}
|
|
557
|
+
let raw: string;
|
|
558
|
+
try {
|
|
559
|
+
raw = fs.readFileSync(filePath, "utf8");
|
|
560
|
+
} catch (error) {
|
|
561
|
+
return failure(
|
|
562
|
+
filePath,
|
|
563
|
+
"io",
|
|
564
|
+
error instanceof Error ? error.message : `Unable to read ${filePath}`,
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
const events: UltraPlanAuthoringPipelineEvent[] = [];
|
|
568
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
569
|
+
if (!line) continue;
|
|
570
|
+
let parsed: unknown;
|
|
571
|
+
try {
|
|
572
|
+
parsed = JSON.parse(line);
|
|
573
|
+
} catch {
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
const validation = validateUltraPlanAuthoringPipelineEvent(parsed);
|
|
577
|
+
if (validation.ok) {
|
|
578
|
+
events.push(validation.value);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return success(events);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Convenience: returns whether an authoring directory exists for the session. Cheap check
|
|
586
|
+
* used by the resume picker.
|
|
587
|
+
*/
|
|
588
|
+
export function hasAuthoringWorkspace(
|
|
589
|
+
paths: PlatformPaths,
|
|
590
|
+
cwd: string,
|
|
591
|
+
sessionId: string,
|
|
592
|
+
): boolean {
|
|
593
|
+
return fs.existsSync(getUltraplanAuthoringDir(paths, cwd, sessionId));
|
|
594
|
+
}
|