fix: remove stale M001/M002 milestone dirs; fix dispatch-guard circular dep; fix telemetry normalization
- Remove stale .sf/milestones/M001/ and M002/ (not in DB, were blocking dispatch) - dispatch-guard.js: import findMilestoneIds from milestone-ids.js directly (not via guided-flow.js, which is in the circular-dep cluster) - auto.js: normalize 'Cannot dispatch' → prior-slice-blocker, 'SF resources updated' → resources-stale, 'Stuck:' → stuck in telemetry (was silently bucketing as 'other') Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
ea360f6ad2
commit
a3f2479a4c
37 changed files with 1340 additions and 769 deletions
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"lastFullVacuumAt": "2026-05-09T17:40:16.686Z"
|
||||
"lastFullVacuumAt": "2026-05-09T23:40:22.903Z"
|
||||
}
|
||||
|
|
|
|||
BIN
.sf/backups/db/sf.db.2026-05-09T23-29-17-802Z
Normal file
BIN
.sf/backups/db/sf.db.2026-05-09T23-29-17-802Z
Normal file
Binary file not shown.
BIN
.sf/metrics.db
BIN
.sf/metrics.db
Binary file not shown.
Binary file not shown.
Binary file not shown.
452
scaffold_files_content.txt
Normal file
452
scaffold_files_content.txt
Normal file
|
|
@ -0,0 +1,452 @@
|
|||
export const SCAFFOLD_FILES = [
|
||||
{
|
||||
path: ".siftignore",
|
||||
content: `.git/**
|
||||
.sf/**
|
||||
.bg-shell/**
|
||||
.pytest_cache/**
|
||||
.venv/**
|
||||
venv/**
|
||||
node_modules/**
|
||||
**/node_modules/**
|
||||
**/__pycache__/**
|
||||
*.pyc
|
||||
*.egg-info/**
|
||||
build/**
|
||||
dist/**
|
||||
target/**
|
||||
vendor/**
|
||||
coverage/**
|
||||
.cache/**
|
||||
tmp/**
|
||||
*.log
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "AGENTS.md",
|
||||
content: `# Agent Map
|
||||
|
||||
Keep this file short. Use it as a table of contents for agents and humans.
|
||||
|
||||
- Treat the repo as a purpose-to-software pipeline: intent -> purpose/consumer/contract/evidence -> tests -> implementation -> verification.
|
||||
- Read \`ARCHITECTURE.md\` first for the system map and invariants.
|
||||
- Read \`docs/PLANS.md\` and \`docs/exec-plans/active/\` for current work.
|
||||
- Read \`docs/QUALITY_SCORE.md\`, \`docs/RELIABILITY.md\`, and \`docs/SECURITY.md\` before changing production behavior.
|
||||
- Put durable product decisions in \`docs/product-specs/\`.
|
||||
- Put durable design and architecture decisions in \`docs/design-docs/\`.
|
||||
- Put generated reference material in \`docs/generated/\`.
|
||||
- Use \`docs/RECORDS_KEEPER.md\` as the repo-order checklist after meaningful changes.
|
||||
- Use the \`records-keeper\` skill when repo docs, plans, or architecture records need triage.
|
||||
- Follow deeper \`AGENTS.md\` files when present. The closest one to the changed file wins.
|
||||
|
||||
Before implementation, inspect the relevant docs and source files, state observed facts before inferred facts, name the real consumer, and define the command or eval that proves the change.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "src/AGENTS.md",
|
||||
content: `# Source Agent Notes
|
||||
|
||||
- Start by mapping the owning module and its tests.
|
||||
- Preserve existing public contracts unless the active plan explicitly changes them.
|
||||
- Prefer typed/domain helpers over ad hoc parsing or duplicated logic.
|
||||
- Keep edits scoped to the smallest module boundary that satisfies the plan.
|
||||
- Update \`ARCHITECTURE.md\` when a source change creates a new subsystem or invariant.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "tests/AGENTS.md",
|
||||
content: `# Test Agent Notes
|
||||
|
||||
- Treat tests as executable specs, not coverage decoration.
|
||||
- Add regression tests for changed behavior and failure modes.
|
||||
- Prefer focused tests that name the behavior under test.
|
||||
- Include the exact verification command in the plan or completion summary.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "ARCHITECTURE.md",
|
||||
content: `# Architecture
|
||||
|
||||
This file is the short map of the codebase. Keep it current and compact.
|
||||
|
||||
## Purpose
|
||||
|
||||
Describe the product, its users, and the job this repository exists to do.
|
||||
|
||||
## Codemap
|
||||
|
||||
- \`src/\`: primary implementation.
|
||||
- \`tests/\`: behavior and regression coverage.
|
||||
- \`docs/\`: durable product, design, plan, reliability, and security context.
|
||||
|
||||
## Invariants
|
||||
|
||||
- Prefer small, named modules with clear ownership.
|
||||
- Behavior changes need tests or an explicit eval.
|
||||
- Keep generated artifacts out of hand-written design docs.
|
||||
- Update this map when new top-level concepts or directories become important.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/design-docs/index.md",
|
||||
content: `# Design Docs
|
||||
|
||||
Durable design decisions live here. Link active proposals, completed decisions, and rejected alternatives.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/AGENTS.md",
|
||||
content: `# Docs Agent Notes
|
||||
|
||||
- Docs are the durable project memory. Keep them concise, navigable, and current.
|
||||
- Treat \`docs/adr/0000-purpose-to-software-compiler.md\` as the root SF product contract.
|
||||
- Put stable decisions here; keep transient execution state in active plans.
|
||||
- Prefer links to source paths, commands, and eval artifacts over broad prose.
|
||||
- When docs and code disagree, inspect the code and update the stale document.
|
||||
- Run the records keeper checklist in \`RECORDS_KEEPER.md\` after meaningful code, product, or architecture changes.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/records/AGENTS.md",
|
||||
content: `# Records Agent Notes
|
||||
|
||||
- Keep repository memory ordered, current, and easy to inspect.
|
||||
- Prefer moving durable facts to the narrowest canonical document over duplicating them.
|
||||
- Preserve historical decisions; mark superseded records instead of deleting useful context.
|
||||
- Escalate conflicts between docs and source by citing the exact files that disagree.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/records/index.md",
|
||||
content: `# Records
|
||||
|
||||
This folder holds repo-memory audits, decision ledgers, context-gardening notes, and records-keeper outputs.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/RECORDS_KEEPER.md",
|
||||
content: `# Records Keeper
|
||||
|
||||
The records keeper keeps repo memory ordered after meaningful changes. Run this checklist at milestone close, after architecture changes, after product behavior changes, and whenever docs/source disagree.
|
||||
|
||||
Use the \`records-keeper\` skill for this workflow when SF skills are available. Use \`context-doctor\` instead when stale state lives under \`.sf/\` or the memory store.
|
||||
|
||||
## Canonical Homes
|
||||
|
||||
- Root \`AGENTS.md\`: short routing map for agents.
|
||||
- \`ARCHITECTURE.md\`: short system map, boundaries, invariants, critical flows, and verification.
|
||||
- \`docs/product-specs/\`: durable user-facing behavior and product decisions.
|
||||
- \`docs/design-docs/\`: durable design and architecture decisions.
|
||||
- \`docs/exec-plans/\`: active/completed work plans and technical debt.
|
||||
- \`docs/generated/\`: generated references only.
|
||||
- \`docs/records/\`: audits, ledgers, and context-gardening outputs.
|
||||
|
||||
## Checklist
|
||||
|
||||
- Root map is current: \`AGENTS.md\` points to the right canonical docs and local \`AGENTS.md\` files.
|
||||
- Architecture is current: new subsystems, boundaries, invariants, data/state, or critical flows are reflected in \`ARCHITECTURE.md\`.
|
||||
- Product specs are current: user-visible behavior changes are reflected in \`docs/product-specs/\`.
|
||||
- Execution plans are filed: active work is in \`docs/exec-plans/active/\`; completed summaries and evidence are in \`docs/exec-plans/completed/\`.
|
||||
- Debt is visible: discovered cleanup is listed in \`docs/exec-plans/tech-debt-tracker.md\`.
|
||||
- Generated docs are marked: generated material stays under \`docs/generated/\` or clearly says how to regenerate it.
|
||||
- Contradictions are resolved: stale docs are updated or marked superseded with links to the source of truth.
|
||||
- Verification is recorded: changed checks, evals, and commands are listed in the relevant plan or quality document.
|
||||
|
||||
## Output
|
||||
|
||||
When records work is non-trivial, write a dated note under \`docs/records/\` with:
|
||||
|
||||
- What changed.
|
||||
- What canonical docs were updated.
|
||||
- What contradictions were found.
|
||||
- What remains unresolved.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/design-docs/AGENTS.md",
|
||||
content: `# Design Doc Agent Notes
|
||||
|
||||
- Capture problem, context, options, decision, consequences, and validation.
|
||||
- Separate observed facts from inferred product or architecture intent.
|
||||
- Record rejected alternatives when they would prevent repeated debate.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/design-docs/core-beliefs.md",
|
||||
content: `# Core Beliefs
|
||||
|
||||
- The repo should explain itself to humans and agents.
|
||||
- Plans should carry acceptance criteria, falsifiers, and verification commands.
|
||||
- Architecture should be mechanically checkable where possible.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/exec-plans/active/index.md",
|
||||
content: `# Active Execution Plans
|
||||
|
||||
Link active plans here. Each plan should state purpose, scope, tasks, acceptance criteria, and verification.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/exec-plans/AGENTS.md",
|
||||
content: `# Execution Plan Agent Notes
|
||||
|
||||
- Every plan needs purpose, scope, tasks, acceptance criteria, falsifier, and verification.
|
||||
- Active plans live in \`active/\`; completed evidence summaries live in \`completed/\`.
|
||||
- Add discovered cleanup to \`tech-debt-tracker.md\` instead of hiding it in chat.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/exec-plans/completed/index.md",
|
||||
content: `# Completed Execution Plans
|
||||
|
||||
Move finished plan summaries here with evidence links and follow-up debt.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/exec-plans/tech-debt-tracker.md",
|
||||
content: `# Tech Debt Tracker
|
||||
|
||||
Track cleanup discovered during implementation. Include owner, impact, proposed fix, and verification.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/generated/db-schema.md",
|
||||
content: `# Database Schema
|
||||
|
||||
Generated or refreshed schema notes belong here. Do not hand-maintain stale schema copies.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/product-specs/index.md",
|
||||
content: `# Product Specs
|
||||
|
||||
Durable user-facing behavior, workflows, and product decisions live here.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/product-specs/AGENTS.md",
|
||||
content: `# Product Spec Agent Notes
|
||||
|
||||
- Describe the user, job-to-be-done, workflow, edge cases, and non-goals.
|
||||
- Keep implementation details out unless they are product-visible constraints.
|
||||
- Update specs when behavior changes, especially onboarding, permissions, billing, or destructive actions.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/product-specs/new-user-onboarding.md",
|
||||
content: `# New User Onboarding
|
||||
|
||||
Describe the first-run experience, success criteria, and failure states when this product has an onboarding flow.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/references/design-system-reference-llms.txt",
|
||||
content: `Reference slot for design-system guidance intended for LLM consumption.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/references/nixpacks-llms.txt",
|
||||
content: `Reference slot for Nixpacks deployment/build guidance intended for LLM consumption.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/references/uv-llms.txt",
|
||||
content: `Reference slot for uv/Python tooling guidance intended for LLM consumption.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/DESIGN.md",
|
||||
content: `# Design
|
||||
|
||||
Record interaction patterns, visual constraints, and design-system usage here.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/FRONTEND.md",
|
||||
content: `# Frontend
|
||||
|
||||
Record frontend architecture, component ownership, accessibility constraints, and browser support here.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/PLANS.md",
|
||||
content: `# Plans
|
||||
|
||||
Use this as the index for current and upcoming work. Link detailed plans in \`docs/exec-plans/\`.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/PRODUCT_SENSE.md",
|
||||
content: `# Product Sense
|
||||
|
||||
Capture user goals, non-goals, tradeoffs, and examples of good product judgment for this repo.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/QUALITY_SCORE.md",
|
||||
content: `# Quality Score
|
||||
|
||||
Define what good looks like for this repo. Include fast checks, slow checks, evals, and known blind spots.
|
||||
|
||||
Use these principles:
|
||||
|
||||
- Make code legible to agents with semantic names and explicit boundaries.
|
||||
- Prefer small, testable modules over files that require broad context to edit.
|
||||
- Enforce style, architecture, and reliability rules mechanically where possible.
|
||||
- Keep a cleanup loop for stale docs, generated artifacts, and accumulated implementation debt.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/RELIABILITY.md",
|
||||
content: `# Reliability
|
||||
|
||||
Document expected failure modes, recovery paths, observability, and release checks here.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/SECURITY.md",
|
||||
content: `# Security
|
||||
|
||||
Document trust boundaries, secrets handling, dependency risk, and security review requirements here.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/design-docs/ADR-TEMPLATE.md",
|
||||
content: `# ADR-NNN: Title
|
||||
|
||||
**Status:** Proposed | Accepted | Rejected | Superseded by ADR-NNN
|
||||
**Date:** YYYY-MM-DD
|
||||
|
||||
## Context
|
||||
|
||||
What is the problem or situation that requires a decision? Include constraints and the forces at play.
|
||||
|
||||
## Decision
|
||||
|
||||
What is the change being made or the approach being adopted?
|
||||
|
||||
## Consequences
|
||||
|
||||
What becomes easier or harder after this decision? Include positive and negative outcomes.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
What other options were evaluated and why were they not chosen?
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/harness/AGENTS.md",
|
||||
content: `# Harness Agent Notes
|
||||
|
||||
The harness is SF-local operational scaffolding the agent can read and verify against.
|
||||
|
||||
- \`specs/\`: behavior contracts. Each spec states what "done" looks like and the command that proves it.
|
||||
- \`evals/\`: task definitions for behaviors tests cannot cover — model output quality, multi-turn flows, agent decisions.
|
||||
- \`graders/\`: reusable grader scripts (code-based checks, LLM-judge prompts used by evals).
|
||||
|
||||
**Rule:** Before marking a task done, run the relevant spec's verification command. Record the result in the completion summary or execution plan.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/harness/specs/AGENTS.md",
|
||||
content: `# Harness Specs Agent Notes
|
||||
|
||||
Each spec file in this directory:
|
||||
|
||||
- States the behavior being specified (not the implementation).
|
||||
- Includes the exact command that proves the spec passes.
|
||||
- Is referenced by the relevant execution plan or ADR.
|
||||
|
||||
Write the spec before implementation. Run it after. Record the result.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/harness/specs/bootstrap.md",
|
||||
content: `# Bootstrap Spec: Agent Legibility
|
||||
|
||||
Verifies that this repo is minimally agent-legible.
|
||||
|
||||
## Criteria
|
||||
|
||||
- [ ] \`AGENTS.md\` exists at repo root and is non-empty.
|
||||
- [ ] \`ARCHITECTURE.md\` exists at repo root and is non-empty.
|
||||
- [ ] \`docs/exec-plans/active/\` exists.
|
||||
- [ ] \`docs/exec-plans/tech-debt-tracker.md\` exists.
|
||||
- [ ] \`docs/design-docs/ADR-TEMPLATE.md\` exists.
|
||||
|
||||
## Verification command
|
||||
|
||||
\`\`\`bash
|
||||
for f in AGENTS.md ARCHITECTURE.md docs/exec-plans/active/index.md docs/exec-plans/tech-debt-tracker.md docs/design-docs/ADR-TEMPLATE.md .sf/harness/specs/bootstrap.md; do [ -s "$f" ] && echo "OK: $f" || echo "MISSING: $f"; done
|
||||
\`\`\`
|
||||
|
||||
All lines should start with \`OK:\` for the bootstrap spec to pass.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/harness/evals/AGENTS.md",
|
||||
content: `# Harness Evals Agent Notes
|
||||
|
||||
Evals verify behavior that unit tests cannot cover — model output quality, agent decisions, multi-turn flows.
|
||||
|
||||
Each eval should include:
|
||||
- The input fixture or prompt
|
||||
- The expected output or scoring rubric
|
||||
- The command to run it (\`promptfoo eval\`, custom script, etc.)
|
||||
|
||||
Keep evals deterministic where possible. Log results to \`docs/records/\` at milestone close.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/harness/graders/AGENTS.md",
|
||||
content: `# Harness Graders Agent Notes
|
||||
|
||||
Graders are reusable scripts or prompts that score eval outputs.
|
||||
|
||||
- Code-based graders: shell scripts or test files that check structured outputs deterministically.
|
||||
- LLM-judge graders: prompt templates that ask a model to score free-text output against a rubric.
|
||||
|
||||
Prefer code-based graders. Add LLM-judge graders only when deterministic checking is impossible.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/PRINCIPLES.md",
|
||||
content: `# Principles
|
||||
|
||||
Durable design philosophy. Things this codebase believes are true.
|
||||
|
||||
Add entries as you make decisions. Each entry: 1-2 sentences. Cite the rationale (the why, not just the what).
|
||||
|
||||
## Examples
|
||||
|
||||
- (replace with your own)
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/TASTE.md",
|
||||
content: `# Taste
|
||||
|
||||
What good code looks like here. Idioms, conventions, "we prefer X over Y" calls.
|
||||
|
||||
Add entries as you notice patterns worth preserving. Each entry: 1-2 sentences with a concrete example.
|
||||
|
||||
## Examples
|
||||
|
||||
- (replace with your own)
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/ANTI-GOALS.md",
|
||||
content: `# Anti-goals
|
||||
|
||||
What we explicitly DON'T want. Things that look attractive but we've decided against.
|
||||
|
||||
This is gold — most wrong agent calls come from not knowing what to avoid. Each entry: 1-2 sentences with the rationale.
|
||||
|
||||
## Examples
|
||||
|
||||
- (replace with your own)
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
|
@ -38,7 +38,7 @@ try {
|
|||
/\/tests?\//,
|
||||
],
|
||||
detectiveOptions: {
|
||||
es6: { mixedImports: true },
|
||||
es6: { mixedImports: true, skipAsyncImports: true },
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
|
|
|
|||
42
src/resources/extensions/bg-shell/bg-events.js
Normal file
42
src/resources/extensions/bg-shell/bg-events.js
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* Shared bg-shell event and alert primitives.
|
||||
*
|
||||
* Extracted from process-manager.js to break the circular dependencies:
|
||||
* output-formatter.js ↔ process-manager.js (cycle 8)
|
||||
* readiness-detector.js ↔ process-manager.js (cycle 9)
|
||||
*
|
||||
* Both output-formatter.js and readiness-detector.js import addEvent/pushAlert
|
||||
* from here instead of from process-manager.js, removing the back-edges.
|
||||
*
|
||||
* Purpose: append timestamped events to a bg process record and buffer alerts
|
||||
* for injection into the next agent context turn.
|
||||
*
|
||||
* Consumer: output-formatter.js, process-manager.js, readiness-detector.js.
|
||||
*/
|
||||
|
||||
import { MAX_EVENTS } from "./types.js";
|
||||
|
||||
/** Pending alerts to inject into the next agent context */
|
||||
export let pendingAlerts = [];
|
||||
const MAX_PENDING_ALERTS = 50;
|
||||
|
||||
/** Replace the pendingAlerts array (used by the extension entry point) */
|
||||
export function setPendingAlerts(alerts) {
|
||||
pendingAlerts = alerts;
|
||||
}
|
||||
|
||||
export function addEvent(bg, event) {
|
||||
const ev = { ...event, timestamp: Date.now() };
|
||||
bg.events.push(ev);
|
||||
if (bg.events.length > MAX_EVENTS) {
|
||||
bg.events.splice(0, bg.events.length - MAX_EVENTS);
|
||||
}
|
||||
}
|
||||
|
||||
export function pushAlert(bg, message) {
|
||||
const prefix = bg ? `[bg:${bg.id} ${bg.label}] ` : "";
|
||||
pendingAlerts.push(`${prefix}${message}`);
|
||||
if (pendingAlerts.length > MAX_PENDING_ALERTS) {
|
||||
pendingAlerts.splice(0, pendingAlerts.length - MAX_PENDING_ALERTS);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ import {
|
|||
DEFAULT_MAX_LINES,
|
||||
truncateHead,
|
||||
} from "@singularity-forge/pi-coding-agent";
|
||||
import { addEvent, pushAlert } from "./process-manager.js";
|
||||
import { addEvent, pushAlert } from "./bg-events.js";
|
||||
import { transitionToReady } from "./readiness-detector.js";
|
||||
import {
|
||||
BUILD_COMPLETE_PATTERN_UNION,
|
||||
|
|
|
|||
|
|
@ -11,19 +11,21 @@ import {
|
|||
sanitizeCommand,
|
||||
} from "@singularity-forge/pi-coding-agent";
|
||||
import { rewriteCommandWithRtk } from "../shared/rtk.js";
|
||||
import {
|
||||
addEvent,
|
||||
pendingAlerts,
|
||||
pushAlert,
|
||||
setPendingAlerts,
|
||||
} from "./bg-events.js";
|
||||
import { analyzeLine } from "./output-formatter.js";
|
||||
import { startPortProbing, transitionToReady } from "./readiness-detector.js";
|
||||
import { DEAD_PROCESS_TTL, MAX_BUFFER_LINES, MAX_EVENTS } from "./types.js";
|
||||
import { DEAD_PROCESS_TTL, MAX_BUFFER_LINES } from "./types.js";
|
||||
import { formatUptime, restoreWindowsVTInput } from "./utilities.js";
|
||||
// Re-export event/alert helpers so existing consumers (bg-shell-lifecycle.js)
|
||||
// continue to work without changing their import paths.
|
||||
export { addEvent, pendingAlerts, pushAlert, setPendingAlerts };
|
||||
// ── Process Registry ───────────────────────────────────────────────────────
|
||||
export const processes = new Map();
|
||||
/** Pending alerts to inject into the next agent context */
|
||||
export let pendingAlerts = [];
|
||||
const MAX_PENDING_ALERTS = 50;
|
||||
/** Replace the pendingAlerts array (used by the extension entry point) */
|
||||
export function setPendingAlerts(alerts) {
|
||||
pendingAlerts = alerts;
|
||||
}
|
||||
export function addOutputLine(bg, stream, line) {
|
||||
bg.output.push({ stream, line, ts: Date.now() });
|
||||
if (stream === "stdout") bg.stdoutLineCount++;
|
||||
|
|
@ -35,20 +37,6 @@ export function addOutputLine(bg, stream, line) {
|
|||
bg.lastReadIndex = Math.max(0, bg.lastReadIndex - excess);
|
||||
}
|
||||
}
|
||||
export function addEvent(bg, event) {
|
||||
const ev = { ...event, timestamp: Date.now() };
|
||||
bg.events.push(ev);
|
||||
if (bg.events.length > MAX_EVENTS) {
|
||||
bg.events.splice(0, bg.events.length - MAX_EVENTS);
|
||||
}
|
||||
}
|
||||
export function pushAlert(bg, message) {
|
||||
const prefix = bg ? `[bg:${bg.id} ${bg.label}] ` : "";
|
||||
pendingAlerts.push(`${prefix}${message}`);
|
||||
if (pendingAlerts.length > MAX_PENDING_ALERTS) {
|
||||
pendingAlerts.splice(0, pendingAlerts.length - MAX_PENDING_ALERTS);
|
||||
}
|
||||
}
|
||||
export function getInfo(p) {
|
||||
return {
|
||||
id: p.id,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Readiness detection: port probing, pattern matching, wait-for-ready.
|
||||
*/
|
||||
import { createConnection } from "node:net";
|
||||
import { addEvent, pushAlert } from "./process-manager.js";
|
||||
import { addEvent, pushAlert } from "./bg-events.js";
|
||||
import {
|
||||
DEFAULT_READY_TIMEOUT,
|
||||
PORT_PROBE_TIMEOUT,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ import {
|
|||
stampScaffoldFile,
|
||||
} from "./scaffold-versioning.js";
|
||||
import { logWarning } from "./workflow-logger.js";
|
||||
import { SCAFFOLD_FILES } from "./scaffold-constants.js";
|
||||
export { SCAFFOLD_FILES };
|
||||
|
||||
/**
|
||||
* Files in SCAFFOLD_FILES that intentionally do not carry an inline
|
||||
|
|
@ -37,466 +39,6 @@ const LEGACY_ROOT_HARNESS_TARGETS = {
|
|||
"harness/evals/AGENTS.md": ".sf/harness/evals/AGENTS.md",
|
||||
"harness/graders/AGENTS.md": ".sf/harness/graders/AGENTS.md",
|
||||
};
|
||||
/**
|
||||
* Canonical scaffold file templates SF manages for agent legibility.
|
||||
*
|
||||
* Includes AGENTS.md (routing map), ARCHITECTURE.md (system overview), and docs
|
||||
* tree structure (product specs, design docs, execution plans, records, generated).
|
||||
* Phase C syncs these to disk, stamps them with version markers, and records manifest
|
||||
* entries (ADR-021).
|
||||
*/
|
||||
export const SCAFFOLD_FILES = [
|
||||
{
|
||||
path: ".siftignore",
|
||||
content: `.git/**
|
||||
.sf/**
|
||||
.bg-shell/**
|
||||
.pytest_cache/**
|
||||
.venv/**
|
||||
venv/**
|
||||
node_modules/**
|
||||
**/node_modules/**
|
||||
**/__pycache__/**
|
||||
*.pyc
|
||||
*.egg-info/**
|
||||
build/**
|
||||
dist/**
|
||||
target/**
|
||||
vendor/**
|
||||
coverage/**
|
||||
.cache/**
|
||||
tmp/**
|
||||
*.log
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "AGENTS.md",
|
||||
content: `# Agent Map
|
||||
|
||||
Keep this file short. Use it as a table of contents for agents and humans.
|
||||
|
||||
- Treat the repo as a purpose-to-software pipeline: intent -> purpose/consumer/contract/evidence -> tests -> implementation -> verification.
|
||||
- Read \`ARCHITECTURE.md\` first for the system map and invariants.
|
||||
- Read \`docs/PLANS.md\` and \`docs/exec-plans/active/\` for current work.
|
||||
- Read \`docs/QUALITY_SCORE.md\`, \`docs/RELIABILITY.md\`, and \`docs/SECURITY.md\` before changing production behavior.
|
||||
- Put durable product decisions in \`docs/product-specs/\`.
|
||||
- Put durable design and architecture decisions in \`docs/design-docs/\`.
|
||||
- Put generated reference material in \`docs/generated/\`.
|
||||
- Use \`docs/RECORDS_KEEPER.md\` as the repo-order checklist after meaningful changes.
|
||||
- Use the \`records-keeper\` skill when repo docs, plans, or architecture records need triage.
|
||||
- Follow deeper \`AGENTS.md\` files when present. The closest one to the changed file wins.
|
||||
|
||||
Before implementation, inspect the relevant docs and source files, state observed facts before inferred facts, name the real consumer, and define the command or eval that proves the change.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "src/AGENTS.md",
|
||||
content: `# Source Agent Notes
|
||||
|
||||
- Start by mapping the owning module and its tests.
|
||||
- Preserve existing public contracts unless the active plan explicitly changes them.
|
||||
- Prefer typed/domain helpers over ad hoc parsing or duplicated logic.
|
||||
- Keep edits scoped to the smallest module boundary that satisfies the plan.
|
||||
- Update \`ARCHITECTURE.md\` when a source change creates a new subsystem or invariant.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "tests/AGENTS.md",
|
||||
content: `# Test Agent Notes
|
||||
|
||||
- Treat tests as executable specs, not coverage decoration.
|
||||
- Add regression tests for changed behavior and failure modes.
|
||||
- Prefer focused tests that name the behavior under test.
|
||||
- Include the exact verification command in the plan or completion summary.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "ARCHITECTURE.md",
|
||||
content: `# Architecture
|
||||
|
||||
This file is the short map of the codebase. Keep it current and compact.
|
||||
|
||||
## Purpose
|
||||
|
||||
Describe the product, its users, and the job this repository exists to do.
|
||||
|
||||
## Codemap
|
||||
|
||||
- \`src/\`: primary implementation.
|
||||
- \`tests/\`: behavior and regression coverage.
|
||||
- \`docs/\`: durable product, design, plan, reliability, and security context.
|
||||
|
||||
## Invariants
|
||||
|
||||
- Prefer small, named modules with clear ownership.
|
||||
- Behavior changes need tests or an explicit eval.
|
||||
- Keep generated artifacts out of hand-written design docs.
|
||||
- Update this map when new top-level concepts or directories become important.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/design-docs/index.md",
|
||||
content: `# Design Docs
|
||||
|
||||
Durable design decisions live here. Link active proposals, completed decisions, and rejected alternatives.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/AGENTS.md",
|
||||
content: `# Docs Agent Notes
|
||||
|
||||
- Docs are the durable project memory. Keep them concise, navigable, and current.
|
||||
- Treat \`docs/adr/0000-purpose-to-software-compiler.md\` as the root SF product contract.
|
||||
- Put stable decisions here; keep transient execution state in active plans.
|
||||
- Prefer links to source paths, commands, and eval artifacts over broad prose.
|
||||
- When docs and code disagree, inspect the code and update the stale document.
|
||||
- Run the records keeper checklist in \`RECORDS_KEEPER.md\` after meaningful code, product, or architecture changes.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/records/AGENTS.md",
|
||||
content: `# Records Agent Notes
|
||||
|
||||
- Keep repository memory ordered, current, and easy to inspect.
|
||||
- Prefer moving durable facts to the narrowest canonical document over duplicating them.
|
||||
- Preserve historical decisions; mark superseded records instead of deleting useful context.
|
||||
- Escalate conflicts between docs and source by citing the exact files that disagree.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/records/index.md",
|
||||
content: `# Records
|
||||
|
||||
This folder holds repo-memory audits, decision ledgers, context-gardening notes, and records-keeper outputs.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/RECORDS_KEEPER.md",
|
||||
content: `# Records Keeper
|
||||
|
||||
The records keeper keeps repo memory ordered after meaningful changes. Run this checklist at milestone close, after architecture changes, after product behavior changes, and whenever docs/source disagree.
|
||||
|
||||
Use the \`records-keeper\` skill for this workflow when SF skills are available. Use \`context-doctor\` instead when stale state lives under \`.sf/\` or the memory store.
|
||||
|
||||
## Canonical Homes
|
||||
|
||||
- Root \`AGENTS.md\`: short routing map for agents.
|
||||
- \`ARCHITECTURE.md\`: short system map, boundaries, invariants, critical flows, and verification.
|
||||
- \`docs/product-specs/\`: durable user-facing behavior and product decisions.
|
||||
- \`docs/design-docs/\`: durable design and architecture decisions.
|
||||
- \`docs/exec-plans/\`: active/completed work plans and technical debt.
|
||||
- \`docs/generated/\`: generated references only.
|
||||
- \`docs/records/\`: audits, ledgers, and context-gardening outputs.
|
||||
|
||||
## Checklist
|
||||
|
||||
- Root map is current: \`AGENTS.md\` points to the right canonical docs and local \`AGENTS.md\` files.
|
||||
- Architecture is current: new subsystems, boundaries, invariants, data/state, or critical flows are reflected in \`ARCHITECTURE.md\`.
|
||||
- Product specs are current: user-visible behavior changes are reflected in \`docs/product-specs/\`.
|
||||
- Execution plans are filed: active work is in \`docs/exec-plans/active/\`; completed summaries and evidence are in \`docs/exec-plans/completed/\`.
|
||||
- Debt is visible: discovered cleanup is listed in \`docs/exec-plans/tech-debt-tracker.md\`.
|
||||
- Generated docs are marked: generated material stays under \`docs/generated/\` or clearly says how to regenerate it.
|
||||
- Contradictions are resolved: stale docs are updated or marked superseded with links to the source of truth.
|
||||
- Verification is recorded: changed checks, evals, and commands are listed in the relevant plan or quality document.
|
||||
|
||||
## Output
|
||||
|
||||
When records work is non-trivial, write a dated note under \`docs/records/\` with:
|
||||
|
||||
- What changed.
|
||||
- What canonical docs were updated.
|
||||
- What contradictions were found.
|
||||
- What remains unresolved.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/design-docs/AGENTS.md",
|
||||
content: `# Design Doc Agent Notes
|
||||
|
||||
- Capture problem, context, options, decision, consequences, and validation.
|
||||
- Separate observed facts from inferred product or architecture intent.
|
||||
- Record rejected alternatives when they would prevent repeated debate.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/design-docs/core-beliefs.md",
|
||||
content: `# Core Beliefs
|
||||
|
||||
- The repo should explain itself to humans and agents.
|
||||
- Plans should carry acceptance criteria, falsifiers, and verification commands.
|
||||
- Architecture should be mechanically checkable where possible.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/exec-plans/active/index.md",
|
||||
content: `# Active Execution Plans
|
||||
|
||||
Link active plans here. Each plan should state purpose, scope, tasks, acceptance criteria, and verification.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/exec-plans/AGENTS.md",
|
||||
content: `# Execution Plan Agent Notes
|
||||
|
||||
- Every plan needs purpose, scope, tasks, acceptance criteria, falsifier, and verification.
|
||||
- Active plans live in \`active/\`; completed evidence summaries live in \`completed/\`.
|
||||
- Add discovered cleanup to \`tech-debt-tracker.md\` instead of hiding it in chat.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/exec-plans/completed/index.md",
|
||||
content: `# Completed Execution Plans
|
||||
|
||||
Move finished plan summaries here with evidence links and follow-up debt.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/exec-plans/tech-debt-tracker.md",
|
||||
content: `# Tech Debt Tracker
|
||||
|
||||
Track cleanup discovered during implementation. Include owner, impact, proposed fix, and verification.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/generated/db-schema.md",
|
||||
content: `# Database Schema
|
||||
|
||||
Generated or refreshed schema notes belong here. Do not hand-maintain stale schema copies.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/product-specs/index.md",
|
||||
content: `# Product Specs
|
||||
|
||||
Durable user-facing behavior, workflows, and product decisions live here.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/product-specs/AGENTS.md",
|
||||
content: `# Product Spec Agent Notes
|
||||
|
||||
- Describe the user, job-to-be-done, workflow, edge cases, and non-goals.
|
||||
- Keep implementation details out unless they are product-visible constraints.
|
||||
- Update specs when behavior changes, especially onboarding, permissions, billing, or destructive actions.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/product-specs/new-user-onboarding.md",
|
||||
content: `# New User Onboarding
|
||||
|
||||
Describe the first-run experience, success criteria, and failure states when this product has an onboarding flow.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/references/design-system-reference-llms.txt",
|
||||
content: `Reference slot for design-system guidance intended for LLM consumption.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/references/nixpacks-llms.txt",
|
||||
content: `Reference slot for Nixpacks deployment/build guidance intended for LLM consumption.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/references/uv-llms.txt",
|
||||
content: `Reference slot for uv/Python tooling guidance intended for LLM consumption.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/DESIGN.md",
|
||||
content: `# Design
|
||||
|
||||
Record interaction patterns, visual constraints, and design-system usage here.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/FRONTEND.md",
|
||||
content: `# Frontend
|
||||
|
||||
Record frontend architecture, component ownership, accessibility constraints, and browser support here.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/PLANS.md",
|
||||
content: `# Plans
|
||||
|
||||
Use this as the index for current and upcoming work. Link detailed plans in \`docs/exec-plans/\`.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/PRODUCT_SENSE.md",
|
||||
content: `# Product Sense
|
||||
|
||||
Capture user goals, non-goals, tradeoffs, and examples of good product judgment for this repo.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/QUALITY_SCORE.md",
|
||||
content: `# Quality Score
|
||||
|
||||
Define what good looks like for this repo. Include fast checks, slow checks, evals, and known blind spots.
|
||||
|
||||
Use these principles:
|
||||
|
||||
- Make code legible to agents with semantic names and explicit boundaries.
|
||||
- Prefer small, testable modules over files that require broad context to edit.
|
||||
- Enforce style, architecture, and reliability rules mechanically where possible.
|
||||
- Keep a cleanup loop for stale docs, generated artifacts, and accumulated implementation debt.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/RELIABILITY.md",
|
||||
content: `# Reliability
|
||||
|
||||
Document expected failure modes, recovery paths, observability, and release checks here.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/SECURITY.md",
|
||||
content: `# Security
|
||||
|
||||
Document trust boundaries, secrets handling, dependency risk, and security review requirements here.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/design-docs/ADR-TEMPLATE.md",
|
||||
content: `# ADR-NNN: Title
|
||||
|
||||
**Status:** Proposed | Accepted | Rejected | Superseded by ADR-NNN
|
||||
**Date:** YYYY-MM-DD
|
||||
|
||||
## Context
|
||||
|
||||
What is the problem or situation that requires a decision? Include constraints and the forces at play.
|
||||
|
||||
## Decision
|
||||
|
||||
What is the change being made or the approach being adopted?
|
||||
|
||||
## Consequences
|
||||
|
||||
What becomes easier or harder after this decision? Include positive and negative outcomes.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
What other options were evaluated and why were they not chosen?
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/harness/AGENTS.md",
|
||||
content: `# Harness Agent Notes
|
||||
|
||||
The harness is SF-local operational scaffolding the agent can read and verify against.
|
||||
|
||||
- \`specs/\`: behavior contracts. Each spec states what "done" looks like and the command that proves it.
|
||||
- \`evals/\`: task definitions for behaviors tests cannot cover — model output quality, multi-turn flows, agent decisions.
|
||||
- \`graders/\`: reusable grader scripts (code-based checks, LLM-judge prompts used by evals).
|
||||
|
||||
**Rule:** Before marking a task done, run the relevant spec's verification command. Record the result in the completion summary or execution plan.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/harness/specs/AGENTS.md",
|
||||
content: `# Harness Specs Agent Notes
|
||||
|
||||
Each spec file in this directory:
|
||||
|
||||
- States the behavior being specified (not the implementation).
|
||||
- Includes the exact command that proves the spec passes.
|
||||
- Is referenced by the relevant execution plan or ADR.
|
||||
|
||||
Write the spec before implementation. Run it after. Record the result.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/harness/specs/bootstrap.md",
|
||||
content: `# Bootstrap Spec: Agent Legibility
|
||||
|
||||
Verifies that this repo is minimally agent-legible.
|
||||
|
||||
## Criteria
|
||||
|
||||
- [ ] \`AGENTS.md\` exists at repo root and is non-empty.
|
||||
- [ ] \`ARCHITECTURE.md\` exists at repo root and is non-empty.
|
||||
- [ ] \`docs/exec-plans/active/\` exists.
|
||||
- [ ] \`docs/exec-plans/tech-debt-tracker.md\` exists.
|
||||
- [ ] \`docs/design-docs/ADR-TEMPLATE.md\` exists.
|
||||
|
||||
## Verification command
|
||||
|
||||
\`\`\`bash
|
||||
for f in AGENTS.md ARCHITECTURE.md docs/exec-plans/active/index.md docs/exec-plans/tech-debt-tracker.md docs/design-docs/ADR-TEMPLATE.md .sf/harness/specs/bootstrap.md; do [ -s "$f" ] && echo "OK: $f" || echo "MISSING: $f"; done
|
||||
\`\`\`
|
||||
|
||||
All lines should start with \`OK:\` for the bootstrap spec to pass.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/harness/evals/AGENTS.md",
|
||||
content: `# Harness Evals Agent Notes
|
||||
|
||||
Evals verify behavior that unit tests cannot cover — model output quality, agent decisions, multi-turn flows.
|
||||
|
||||
Each eval should include:
|
||||
- The input fixture or prompt
|
||||
- The expected output or scoring rubric
|
||||
- The command to run it (\`promptfoo eval\`, custom script, etc.)
|
||||
|
||||
Keep evals deterministic where possible. Log results to \`docs/records/\` at milestone close.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/harness/graders/AGENTS.md",
|
||||
content: `# Harness Graders Agent Notes
|
||||
|
||||
Graders are reusable scripts or prompts that score eval outputs.
|
||||
|
||||
- Code-based graders: shell scripts or test files that check structured outputs deterministically.
|
||||
- LLM-judge graders: prompt templates that ask a model to score free-text output against a rubric.
|
||||
|
||||
Prefer code-based graders. Add LLM-judge graders only when deterministic checking is impossible.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/PRINCIPLES.md",
|
||||
content: `# Principles
|
||||
|
||||
Durable design philosophy. Things this codebase believes are true.
|
||||
|
||||
Add entries as you make decisions. Each entry: 1-2 sentences. Cite the rationale (the why, not just the what).
|
||||
|
||||
## Examples
|
||||
|
||||
- (replace with your own)
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/TASTE.md",
|
||||
content: `# Taste
|
||||
|
||||
What good code looks like here. Idioms, conventions, "we prefer X over Y" calls.
|
||||
|
||||
Add entries as you notice patterns worth preserving. Each entry: 1-2 sentences with a concrete example.
|
||||
|
||||
## Examples
|
||||
|
||||
- (replace with your own)
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/ANTI-GOALS.md",
|
||||
content: `# Anti-goals
|
||||
|
||||
What we explicitly DON'T want. Things that look attractive but we've decided against.
|
||||
|
||||
This is gold — most wrong agent calls come from not knowing what to avoid. Each entry: 1-2 sentences with the rationale.
|
||||
|
||||
## Examples
|
||||
|
||||
- (replace with your own)
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
function pruneEmptyDir(path) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1088,6 +1088,12 @@ export async function stopAuto(ctx, pi, reason) {
|
|||
const rawReason = reason ?? "stop";
|
||||
const normalizedReason = rawReason.startsWith("Blocked:")
|
||||
? "blocked"
|
||||
: rawReason.startsWith("Cannot dispatch")
|
||||
? "prior-slice-blocker"
|
||||
: rawReason.startsWith("SF resources were updated")
|
||||
? "resources-stale"
|
||||
: rawReason.startsWith("Stuck:")
|
||||
? "stuck"
|
||||
: rawReason.startsWith("Merge conflict")
|
||||
? "merge-conflict"
|
||||
: rawReason.startsWith("Merge error") ||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,15 @@ import {
|
|||
loadProjectSFPreferences,
|
||||
resolveAllSkillReferences,
|
||||
} from "./preferences.js";
|
||||
// Re-export from thin shared module so external importers keep working.
|
||||
export {
|
||||
serializePreferencesToFrontmatter,
|
||||
yamlSafeString,
|
||||
} from "./preferences-serializer.js";
|
||||
import {
|
||||
serializePreferencesToFrontmatter,
|
||||
yamlSafeString,
|
||||
} from "./preferences-serializer.js";
|
||||
|
||||
/** Extract body content after frontmatter closing delimiter, or null if none. */
|
||||
function extractBodyAfterFrontmatter(content) {
|
||||
|
|
@ -840,113 +849,6 @@ export async function handlePrefsWizard(ctx, scope) {
|
|||
await ctx.reload();
|
||||
ctx.ui.notify(`Saved ${scope} preferences to ${path}`, "info");
|
||||
}
|
||||
/** Wrap a YAML value in double quotes if it contains special characters. */
|
||||
export function yamlSafeString(val) {
|
||||
if (typeof val !== "string") return String(val);
|
||||
if (/[:#{[\]'"`,|>&*!?@%]/.test(val) || val.trim() !== val || val === "") {
|
||||
return `"${val.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
export function serializePreferencesToFrontmatter(prefs) {
|
||||
const lines = [];
|
||||
function serializeValue(key, value, indent) {
|
||||
const prefix = " ".repeat(indent);
|
||||
if (value === null || value === undefined) return;
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length === 0) {
|
||||
return; // Omit empty arrays — avoids parse/serialize cycle bug with "[]" strings
|
||||
}
|
||||
lines.push(`${prefix}${key}:`);
|
||||
for (const item of value) {
|
||||
if (typeof item === "object" && item !== null) {
|
||||
const entries = Object.entries(item);
|
||||
if (entries.length > 0) {
|
||||
const [firstKey, firstVal] = entries[0];
|
||||
lines.push(`${prefix} - ${firstKey}: ${yamlSafeString(firstVal)}`);
|
||||
for (let i = 1; i < entries.length; i++) {
|
||||
const [k, v] = entries[i];
|
||||
if (Array.isArray(v)) {
|
||||
lines.push(`${prefix} ${k}:`);
|
||||
for (const arrItem of v) {
|
||||
lines.push(`${prefix} - ${yamlSafeString(arrItem)}`);
|
||||
}
|
||||
} else {
|
||||
lines.push(`${prefix} ${k}: ${yamlSafeString(v)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lines.push(`${prefix} - ${yamlSafeString(item)}`);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
const entries = Object.entries(value);
|
||||
if (entries.length === 0) {
|
||||
return; // Omit empty objects — avoids parse/serialize cycle bug with "{}" strings
|
||||
}
|
||||
lines.push(`${prefix}${key}:`);
|
||||
for (const [k, v] of entries) {
|
||||
serializeValue(k, v, indent + 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
lines.push(`${prefix}${key}: ${yamlSafeString(value)}`);
|
||||
}
|
||||
// Ordered keys for consistent output
|
||||
const orderedKeys = [
|
||||
"version",
|
||||
"mode",
|
||||
"always_use_skills",
|
||||
"prefer_skills",
|
||||
"avoid_skills",
|
||||
"skill_rules",
|
||||
"custom_instructions",
|
||||
"models",
|
||||
"skill_discovery",
|
||||
"skill_staleness_days",
|
||||
"auto_supervisor",
|
||||
"uat_dispatch",
|
||||
"unique_milestone_ids",
|
||||
"budget_ceiling",
|
||||
"budget_enforcement",
|
||||
"context_pause_threshold",
|
||||
"notifications",
|
||||
"cmux",
|
||||
"remote_questions",
|
||||
"git",
|
||||
"post_unit_hooks",
|
||||
"pre_dispatch_hooks",
|
||||
"dynamic_routing",
|
||||
"uok",
|
||||
"token_profile",
|
||||
"phases",
|
||||
"parallel",
|
||||
"auto_visualize",
|
||||
"auto_report",
|
||||
"verification_commands",
|
||||
"verification_auto_fix",
|
||||
"verification_max_retries",
|
||||
"search_provider",
|
||||
"context_selection",
|
||||
];
|
||||
const seen = new Set();
|
||||
for (const key of orderedKeys) {
|
||||
if (key in prefs) {
|
||||
serializeValue(key, prefs[key], 0);
|
||||
seen.add(key);
|
||||
}
|
||||
}
|
||||
// Any remaining keys not in the ordered list
|
||||
for (const [key, value] of Object.entries(prefs)) {
|
||||
if (!seen.has(key)) {
|
||||
serializeValue(key, value, 0);
|
||||
}
|
||||
}
|
||||
return lines.join("\n") + "\n";
|
||||
}
|
||||
export async function ensurePreferencesFile(path, ctx, scope) {
|
||||
if (!existsSync(path)) {
|
||||
const template = await loadFile(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// SF Dispatch Guard — prevents out-of-order slice dispatch
|
||||
import { readFileSync } from "node:fs";
|
||||
import { findMilestoneIds } from "./guided-flow.js";
|
||||
import { findMilestoneIds } from "./milestone-ids.js";
|
||||
import { parseRoadmap } from "./parsers.js";
|
||||
import { resolveMilestoneFile } from "./paths.js";
|
||||
import { getMilestoneSlices, isDbAvailable } from "./sf-db.js";
|
||||
|
|
|
|||
|
|
@ -339,6 +339,11 @@ export async function checkRuntimeHealth(
|
|||
// Non-fatal — activity log check failed
|
||||
}
|
||||
// ── STATE.md health ───────────────────────────────────────────────────
|
||||
// STATE.md is a display-only projection rendered from the DB. No program
|
||||
// logic reads it — staleness does not affect correctness, only human
|
||||
// readability. The engine rewrites it after every DB-mutating operation.
|
||||
// We only fix the missing-file case so the /sf status panel has something
|
||||
// to show on first init.
|
||||
try {
|
||||
const stateFilePath = resolveSfRootFile(basePath, "STATE");
|
||||
const milestonesPath = milestonesDir(basePath);
|
||||
|
|
@ -358,44 +363,6 @@ export async function checkRuntimeHealth(
|
|||
await saveFile(stateFilePath, buildStateMarkdownForCheck(state));
|
||||
fixesApplied.push("created STATE.md from derived state");
|
||||
}
|
||||
} else {
|
||||
// Check if STATE.md is stale by comparing active milestone/slice/phase
|
||||
const currentContent = readFileSync(stateFilePath, "utf-8");
|
||||
const state = await deriveState(basePath);
|
||||
const freshContent = buildStateMarkdownForCheck(state);
|
||||
// Extract key fields for comparison — don't compare full content
|
||||
// since timestamp/formatting differences are normal
|
||||
const extractFields = (content) => {
|
||||
const milestone =
|
||||
content.match(/\*\*Active Milestone:\*\*\s*(.+)/)?.[1]?.trim() ??
|
||||
"";
|
||||
const slice =
|
||||
content.match(/\*\*Active Slice:\*\*\s*(.+)/)?.[1]?.trim() ?? "";
|
||||
const phase =
|
||||
content.match(/\*\*Phase:\*\*\s*(.+)/)?.[1]?.trim() ?? "";
|
||||
return { milestone, slice, phase };
|
||||
};
|
||||
const current = extractFields(currentContent);
|
||||
const fresh = extractFields(freshContent);
|
||||
if (
|
||||
current.milestone !== fresh.milestone ||
|
||||
current.slice !== fresh.slice ||
|
||||
current.phase !== fresh.phase
|
||||
) {
|
||||
issues.push({
|
||||
severity: "warning",
|
||||
code: "state_file_stale",
|
||||
scope: "project",
|
||||
unitId: "project",
|
||||
message: `STATE.md is stale — shows "${current.phase}" but derived state is "${fresh.phase}"`,
|
||||
file: ".sf/STATE.md",
|
||||
fixable: true,
|
||||
});
|
||||
if (shouldFix("state_file_stale")) {
|
||||
await saveFile(stateFilePath, freshContent);
|
||||
fixesApplied.push("rebuilt STATE.md from derived state");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
|
|
|
|||
|
|
@ -926,13 +926,6 @@ export {
|
|||
formatDoctorReportJson,
|
||||
summarizeDoctorIssues,
|
||||
} from "./doctor-format.js";
|
||||
export {
|
||||
computeProgressScore,
|
||||
computeProgressScoreWithContext,
|
||||
formatProgressLine,
|
||||
formatProgressReport,
|
||||
} from "./progress-score.js";
|
||||
|
||||
/**
|
||||
* Characters that are used as delimiters in SF state management documents
|
||||
* and should not appear in milestone or slice titles.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
/**
|
||||
* Shared git constants used across git-service and native-git-bridge.
|
||||
*/
|
||||
/**
|
||||
* Regex that matches valid git branch/ref name characters.
|
||||
* Extracted here to allow preferences-validation.js to validate branch names
|
||||
* without importing the full git-service module (which would create a cycle).
|
||||
*/
|
||||
export const VALID_BRANCH_NAME = /^[a-zA-Z0-9_\-/.]+$/;
|
||||
/**
|
||||
* Environment overlay suppressing interactive git prompts, editors, and git-svn noise.
|
||||
* Set LC_ALL=C for English output so stderr string checks work across locales.
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import {
|
|||
import { getErrorMessage } from "./error-utils.js";
|
||||
import { SF_GIT_ERROR, SF_MERGE_CONFLICT, SFError } from "./errors.js";
|
||||
import { normalizePlannedFileReference } from "./files.js";
|
||||
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
||||
import { GIT_NO_PROMPT_ENV, VALID_BRANCH_NAME } from "./git-constants.js";
|
||||
import { SF_RUNTIME_PATTERNS } from "./git-runtime-patterns.js";
|
||||
import {
|
||||
_resetHasChangesCache,
|
||||
|
|
@ -43,9 +43,7 @@ import {
|
|||
} from "./native-git-bridge.js";
|
||||
import { sfRoot } from "./paths.js";
|
||||
import { loadEffectiveSFPreferences } from "./preferences.js";
|
||||
import { detectWorktreeName } from "./worktree.js";
|
||||
/** Regex for valid git branch names (alphanumeric, hyphens, underscores, slashes). */
|
||||
export const VALID_BRANCH_NAME = /^[a-zA-Z0-9_\-/.]+$/;
|
||||
import { detectWorktreeName } from "./worktree-detect.js";
|
||||
/**
|
||||
* Build a meaningful conventional commit message from task execution context.
|
||||
* Format: `{type}: {description}` (clean conventional commit — no SF IDs in subject).
|
||||
|
|
|
|||
|
|
@ -12,13 +12,11 @@ import { existsSync, readFileSync } from "node:fs";
|
|||
import { join, resolve } from "node:path";
|
||||
import { showNextAction } from "../../shared/tui.js";
|
||||
import { sfRoot } from "../paths.js";
|
||||
import {
|
||||
generatePreview,
|
||||
parsePlanningDirectory,
|
||||
transformToSF,
|
||||
validatePlanningDirectory,
|
||||
writeSFDirectory,
|
||||
} from "./index.js";
|
||||
import { parsePlanningDirectory } from "./parser.js";
|
||||
import { generatePreview } from "./preview.js";
|
||||
import { transformToSF } from "./transformer.js";
|
||||
import { validatePlanningDirectory } from "./validator.js";
|
||||
import { writeSFDirectory } from "./writer.js";
|
||||
|
||||
/** Format preview stats for embedding in the review prompt. */
|
||||
function formatPreviewStats(preview) {
|
||||
|
|
|
|||
22
src/resources/extensions/sf/milestone-id-sort.js
Normal file
22
src/resources/extensions/sf/milestone-id-sort.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* Pure milestone ID sort helpers — no external SF dependencies.
|
||||
*
|
||||
* Extracted from milestone-ids.js to break the circular dependency between
|
||||
* milestone-ids.js and queue-order.js. Both files import from here.
|
||||
*
|
||||
* Purpose: provide extractMilestoneSeq and milestoneIdSort without importing
|
||||
* from either queue-order.js or milestone-ids.js.
|
||||
*
|
||||
* Consumer: milestone-ids.js, queue-order.js.
|
||||
*/
|
||||
|
||||
/** Extract the trailing sequential number from a milestone ID. Returns 0 for non-matches. */
|
||||
export function extractMilestoneSeq(id) {
|
||||
const m = id.match(/^M(\d{3})(?:-[a-z0-9]{6})?$/);
|
||||
return m ? parseInt(m[1], 10) : 0;
|
||||
}
|
||||
|
||||
/** Comparator for sorting milestone IDs by sequential number. */
|
||||
export function milestoneIdSort(a, b) {
|
||||
return extractMilestoneSeq(a) - extractMilestoneSeq(b);
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
import { randomInt } from "node:crypto";
|
||||
import { existsSync, readdirSync } from "node:fs";
|
||||
import { getErrorMessage } from "./error-utils.js";
|
||||
import { extractMilestoneSeq, milestoneIdSort } from "./milestone-id-sort.js";
|
||||
import { milestonesDir } from "./paths.js";
|
||||
import { loadQueueOrder, sortByQueueOrder } from "./queue-order.js";
|
||||
import { logWarning } from "./workflow-logger.js";
|
||||
|
|
@ -14,12 +15,9 @@ import { getAllMilestones } from "./sf-db.js";
|
|||
// ─── Regex ──────────────────────────────────────────────────────────────────
|
||||
/** Matches both classic `M001` and unique `M001-abc123` formats (anchored). */
|
||||
export const MILESTONE_ID_RE = /^M\d{3}(?:-[a-z0-9]{6})?$/;
|
||||
// Re-export sort helpers for consumers that import them from this module.
|
||||
export { extractMilestoneSeq, milestoneIdSort };
|
||||
// ─── Parsing & Extraction ───────────────────────────────────────────────────
|
||||
/** Extract the trailing sequential number from a milestone ID. Returns 0 for non-matches. */
|
||||
export function extractMilestoneSeq(id) {
|
||||
const m = id.match(/^M(\d{3})(?:-[a-z0-9]{6})?$/);
|
||||
return m ? parseInt(m[1], 10) : 0;
|
||||
}
|
||||
/** Structured parse of a milestone ID into optional suffix and sequence number. */
|
||||
export function parseMilestoneId(id) {
|
||||
const m = id.match(/^M(\d{3})(?:-([a-z0-9]{6}))?$/);
|
||||
|
|
@ -29,11 +27,6 @@ export function parseMilestoneId(id) {
|
|||
num: parseInt(m[1], 10),
|
||||
};
|
||||
}
|
||||
// ─── Sorting ────────────────────────────────────────────────────────────────
|
||||
/** Comparator for sorting milestone IDs by sequential number. */
|
||||
export function milestoneIdSort(a, b) {
|
||||
return extractMilestoneSeq(a) - extractMilestoneSeq(b);
|
||||
}
|
||||
// ─── Generation ─────────────────────────────────────────────────────────────
|
||||
/** Generate a 6-char lowercase `[a-z0-9]` suffix using crypto.randomInt(). */
|
||||
export function generateMilestoneSuffix() {
|
||||
|
|
|
|||
|
|
@ -5,10 +5,15 @@
|
|||
* dependency satisfaction and file overlap across slice plans.
|
||||
*/
|
||||
import { findMilestoneIds } from "./guided-flow.js";
|
||||
import { getWorkerStatuses } from "./parallel-orchestrator.js";
|
||||
import { getMilestoneSlices, getSliceTasks, isDbAvailable } from "./sf-db.js";
|
||||
import { deriveState } from "./state.js";
|
||||
|
||||
// Lazy import to break the cycle: parallel-eligibility ↔ parallel-orchestrator
|
||||
let _parallelOrchestrator;
|
||||
async function getParallelOrchestrator() {
|
||||
return (_parallelOrchestrator ??= await import("./parallel-orchestrator.js"));
|
||||
}
|
||||
|
||||
// ─── File Collection ─────────────────────────────────────────────────────────
|
||||
/**
|
||||
* Collect all `filesLikelyTouched` across every slice plan in a milestone.
|
||||
|
|
@ -167,6 +172,7 @@ export async function analyzeParallelEligibility(basePath) {
|
|||
overlappingIds.add(overlap.mid1);
|
||||
overlappingIds.add(overlap.mid2);
|
||||
}
|
||||
const { getWorkerStatuses } = await getParallelOrchestrator();
|
||||
const runningWorkerIds = new Set(
|
||||
getWorkerStatuses(basePath)
|
||||
.filter((w) => w.state === "running")
|
||||
|
|
|
|||
|
|
@ -18,10 +18,25 @@ import {
|
|||
} from "./auto-runaway-guard.js";
|
||||
import { selectByBenchmarks } from "./benchmark-selector.js";
|
||||
import { defaultRoutingConfig } from "./model-router.js";
|
||||
import {
|
||||
getGlobalSFPreferencesPath,
|
||||
loadEffectiveSFPreferences,
|
||||
} from "./preferences.js";
|
||||
|
||||
// ─── Lazy loader — breaks the preferences.js ↔ preferences-models.js cycle ──
|
||||
// preferences.js imports resolveProfileDefaults from here, and needs
|
||||
// loadEffectiveSFPreferences / getGlobalSFPreferencesPath from preferences.js.
|
||||
// Injecting them via _initPrefsLoader (called at the end of preferences.js
|
||||
// module evaluation) avoids the static circular import.
|
||||
let _loadEffectiveSFPreferences;
|
||||
let _getGlobalSFPreferencesPath;
|
||||
/** @internal — called once by preferences.js after its exports are defined. */
|
||||
export function _initPrefsLoader(loadFn, getPathFn) {
|
||||
_loadEffectiveSFPreferences = loadFn;
|
||||
_getGlobalSFPreferencesPath = getPathFn;
|
||||
}
|
||||
function loadEffectiveSFPreferences() {
|
||||
return _loadEffectiveSFPreferences();
|
||||
}
|
||||
function getGlobalSFPreferencesPath() {
|
||||
return _getGlobalSFPreferencesPath();
|
||||
}
|
||||
import { getConfiguredEnvApiKey } from "./provider-env-auth.js";
|
||||
|
||||
const OPENCODE_FREE_MODEL_IDS = new Set([
|
||||
|
|
|
|||
145
src/resources/extensions/sf/preferences-serializer.js
Normal file
145
src/resources/extensions/sf/preferences-serializer.js
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/**
|
||||
* Thin preferences serializer — no intra-repo imports.
|
||||
*
|
||||
* Extracted from commands-prefs-wizard.js so that preferences-template-upgrade.js
|
||||
* can serialize preferences without importing the full wizard module (which
|
||||
* transitively imports preferences.js, creating a cycle through
|
||||
* preferences.js → preferences-template-upgrade.js → commands-prefs-wizard.js → preferences.js).
|
||||
*
|
||||
* commands-prefs-wizard.js re-exports both symbols for back-compat.
|
||||
*
|
||||
* Purpose: convert a raw SFPreferences object to YAML frontmatter text
|
||||
* in a deterministic key order, with YAML-safe quoting for string values.
|
||||
* Consumer: preferences-template-upgrade.js (upgradePreferencesFileIfDrifted),
|
||||
* commands-prefs-wizard.js (wizard configure* functions).
|
||||
*/
|
||||
|
||||
/**
|
||||
* Quote a scalar value for safe inclusion in YAML frontmatter.
|
||||
* Non-string scalars are coerced to string; strings that contain YAML
|
||||
* metacharacters or leading/trailing whitespace are double-quoted.
|
||||
*
|
||||
* @param {unknown} val
|
||||
* @returns {string}
|
||||
*/
|
||||
export function yamlSafeString(val) {
|
||||
if (typeof val !== "string") return String(val);
|
||||
if (/[:#{[\]'"`,|>&*!?@%]/.test(val) || val.trim() !== val || val === "") {
|
||||
return `"${val.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize an SFPreferences object to YAML frontmatter text (without the
|
||||
* surrounding `---` delimiters). Keys are emitted in a canonical order for
|
||||
* stable diffs; unknown keys are appended after the ordered set.
|
||||
*
|
||||
* Purpose: produce the YAML block written between `---` markers in
|
||||
* PREFERENCES.md. Relies only on yamlSafeString — no filesystem or
|
||||
* preferences-loading imports needed.
|
||||
*
|
||||
* Consumer: preferences-template-upgrade.js, commands-prefs-wizard.js.
|
||||
*
|
||||
* @param {Record<string, unknown>} prefs
|
||||
* @returns {string}
|
||||
*/
|
||||
export function serializePreferencesToFrontmatter(prefs) {
|
||||
const lines = [];
|
||||
function serializeValue(key, value, indent) {
|
||||
const prefix = " ".repeat(indent);
|
||||
if (value === null || value === undefined) return;
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length === 0) {
|
||||
return; // Omit empty arrays — avoids parse/serialize cycle bug with "[]" strings
|
||||
}
|
||||
lines.push(`${prefix}${key}:`);
|
||||
for (const item of value) {
|
||||
if (typeof item === "object" && item !== null) {
|
||||
const entries = Object.entries(item);
|
||||
if (entries.length > 0) {
|
||||
const [firstKey, firstVal] = entries[0];
|
||||
lines.push(`${prefix} - ${firstKey}: ${yamlSafeString(firstVal)}`);
|
||||
for (let i = 1; i < entries.length; i++) {
|
||||
const [k, v] = entries[i];
|
||||
if (Array.isArray(v)) {
|
||||
lines.push(`${prefix} ${k}:`);
|
||||
for (const arrItem of v) {
|
||||
lines.push(`${prefix} - ${yamlSafeString(arrItem)}`);
|
||||
}
|
||||
} else {
|
||||
lines.push(`${prefix} ${k}: ${yamlSafeString(v)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lines.push(`${prefix} - ${yamlSafeString(item)}`);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
const entries = Object.entries(value);
|
||||
if (entries.length === 0) {
|
||||
return; // Omit empty objects — avoids parse/serialize cycle bug with "{}" strings
|
||||
}
|
||||
lines.push(`${prefix}${key}:`);
|
||||
for (const [k, v] of entries) {
|
||||
serializeValue(k, v, indent + 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
lines.push(`${prefix}${key}: ${yamlSafeString(value)}`);
|
||||
}
|
||||
// Ordered keys for consistent output
|
||||
const orderedKeys = [
|
||||
"version",
|
||||
"mode",
|
||||
"always_use_skills",
|
||||
"prefer_skills",
|
||||
"avoid_skills",
|
||||
"skill_rules",
|
||||
"custom_instructions",
|
||||
"models",
|
||||
"skill_discovery",
|
||||
"skill_staleness_days",
|
||||
"auto_supervisor",
|
||||
"uat_dispatch",
|
||||
"unique_milestone_ids",
|
||||
"budget_ceiling",
|
||||
"budget_enforcement",
|
||||
"context_pause_threshold",
|
||||
"notifications",
|
||||
"cmux",
|
||||
"remote_questions",
|
||||
"git",
|
||||
"post_unit_hooks",
|
||||
"pre_dispatch_hooks",
|
||||
"dynamic_routing",
|
||||
"uok",
|
||||
"token_profile",
|
||||
"phases",
|
||||
"parallel",
|
||||
"auto_visualize",
|
||||
"auto_report",
|
||||
"verification_commands",
|
||||
"verification_auto_fix",
|
||||
"verification_max_retries",
|
||||
"search_provider",
|
||||
"context_selection",
|
||||
];
|
||||
const seen = new Set();
|
||||
for (const key of orderedKeys) {
|
||||
if (key in prefs) {
|
||||
serializeValue(key, prefs[key], 0);
|
||||
seen.add(key);
|
||||
}
|
||||
}
|
||||
// Any remaining keys not in the ordered list
|
||||
for (const [key, value] of Object.entries(prefs)) {
|
||||
if (!seen.has(key)) {
|
||||
serializeValue(key, value, 0);
|
||||
}
|
||||
}
|
||||
return lines.join("\n") + "\n";
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
* have to read drift advisories; sf keeps its own house in order.
|
||||
*/
|
||||
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { serializePreferencesToFrontmatter } from "./commands-prefs-wizard.js";
|
||||
import { serializePreferencesToFrontmatter } from "./preferences-serializer.js";
|
||||
export function detectTemplateDrift(prefs) {
|
||||
const toVersion = process.env.SF_VERSION || "0.0.0";
|
||||
const fromVersion = prefs.last_synced_with_sf || "0.0.0";
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
* together with any errors and warnings.
|
||||
*/
|
||||
import { normalizeStringArray } from "../shared/format-utils.js";
|
||||
import { VALID_BRANCH_NAME } from "./git-service.js";
|
||||
import { VALID_BRANCH_NAME } from "./git-constants.js";
|
||||
import {
|
||||
CURRENT_PREFERENCES_SCHEMA_VERSION,
|
||||
checkPreferencesDrift,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { dirname, join, resolve } from "node:path";
|
|||
import { parse as parseYaml } from "yaml";
|
||||
import { normalizeStringArray } from "../shared/format-utils.js";
|
||||
import { sfRoot } from "./paths.js";
|
||||
import { resolveProfileDefaults as _resolveProfileDefaults } from "./preferences-models.js";
|
||||
import { resolveProfileDefaults as _resolveProfileDefaults, _initPrefsLoader } from "./preferences-models.js";
|
||||
import { upgradePreferencesFileIfDrifted } from "./preferences-template-upgrade.js";
|
||||
import {
|
||||
formatSkillRef,
|
||||
|
|
@ -923,3 +923,8 @@ export function getHotCacheTurns() {
|
|||
const mode = prefs?.mode ?? "solo";
|
||||
return mode === "team" ? 10 : 5;
|
||||
}
|
||||
|
||||
// Inject the loader functions into preferences-models.js to break the
|
||||
// preferences.js ↔ preferences-models.js circular import. This runs after
|
||||
// all exports in this module are defined, so the injected references are live.
|
||||
_initPrefsLoader(loadEffectiveSFPreferences, getGlobalSFPreferencesPath);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
import { join } from "node:path";
|
||||
import { loadJsonFileOrNull, saveJsonFile } from "./json-persistence.js";
|
||||
import { milestoneIdSort } from "./milestone-ids.js";
|
||||
import { milestoneIdSort } from "./milestone-id-sort.js";
|
||||
import { sfRoot } from "./paths.js";
|
||||
import {
|
||||
getAllMilestones,
|
||||
|
|
|
|||
462
src/resources/extensions/sf/scaffold-constants.js
Normal file
462
src/resources/extensions/sf/scaffold-constants.js
Normal file
|
|
@ -0,0 +1,462 @@
|
|||
/**
|
||||
* Canonical scaffold file list — shared between agentic-docs-scaffold.js and scaffold-drift.js.
|
||||
*
|
||||
* Extracted to break the circular dependency between those two modules.
|
||||
* Neither agentic-docs-scaffold.js nor scaffold-drift.js import from each other;
|
||||
* both import SCAFFOLD_FILES from here.
|
||||
*
|
||||
* Purpose: single source of truth for the files SF manages in every project.
|
||||
* Consumer: agentic-docs-scaffold.js, scaffold-drift.js.
|
||||
*/
|
||||
export const SCAFFOLD_FILES = [
|
||||
{
|
||||
path: ".siftignore",
|
||||
content: `.git/**
|
||||
.sf/**
|
||||
.bg-shell/**
|
||||
.pytest_cache/**
|
||||
.venv/**
|
||||
venv/**
|
||||
node_modules/**
|
||||
**/node_modules/**
|
||||
**/__pycache__/**
|
||||
*.pyc
|
||||
*.egg-info/**
|
||||
build/**
|
||||
dist/**
|
||||
target/**
|
||||
vendor/**
|
||||
coverage/**
|
||||
.cache/**
|
||||
tmp/**
|
||||
*.log
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "AGENTS.md",
|
||||
content: `# Agent Map
|
||||
|
||||
Keep this file short. Use it as a table of contents for agents and humans.
|
||||
|
||||
- Treat the repo as a purpose-to-software pipeline: intent -> purpose/consumer/contract/evidence -> tests -> implementation -> verification.
|
||||
- Read \`ARCHITECTURE.md\` first for the system map and invariants.
|
||||
- Read \`docs/PLANS.md\` and \`docs/exec-plans/active/\` for current work.
|
||||
- Read \`docs/QUALITY_SCORE.md\`, \`docs/RELIABILITY.md\`, and \`docs/SECURITY.md\` before changing production behavior.
|
||||
- Put durable product decisions in \`docs/product-specs/\`.
|
||||
- Put durable design and architecture decisions in \`docs/design-docs/\`.
|
||||
- Put generated reference material in \`docs/generated/\`.
|
||||
- Use \`docs/RECORDS_KEEPER.md\` as the repo-order checklist after meaningful changes.
|
||||
- Use the \`records-keeper\` skill when repo docs, plans, or architecture records need triage.
|
||||
- Follow deeper \`AGENTS.md\` files when present. The closest one to the changed file wins.
|
||||
|
||||
Before implementation, inspect the relevant docs and source files, state observed facts before inferred facts, name the real consumer, and define the command or eval that proves the change.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "src/AGENTS.md",
|
||||
content: `# Source Agent Notes
|
||||
|
||||
- Start by mapping the owning module and its tests.
|
||||
- Preserve existing public contracts unless the active plan explicitly changes them.
|
||||
- Prefer typed/domain helpers over ad hoc parsing or duplicated logic.
|
||||
- Keep edits scoped to the smallest module boundary that satisfies the plan.
|
||||
- Update \`ARCHITECTURE.md\` when a source change creates a new subsystem or invariant.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "tests/AGENTS.md",
|
||||
content: `# Test Agent Notes
|
||||
|
||||
- Treat tests as executable specs, not coverage decoration.
|
||||
- Add regression tests for changed behavior and failure modes.
|
||||
- Prefer focused tests that name the behavior under test.
|
||||
- Include the exact verification command in the plan or completion summary.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "ARCHITECTURE.md",
|
||||
content: `# Architecture
|
||||
|
||||
This file is the short map of the codebase. Keep it current and compact.
|
||||
|
||||
## Purpose
|
||||
|
||||
Describe the product, its users, and the job this repository exists to do.
|
||||
|
||||
## Codemap
|
||||
|
||||
- \`src/\`: primary implementation.
|
||||
- \`tests/\`: behavior and regression coverage.
|
||||
- \`docs/\`: durable product, design, plan, reliability, and security context.
|
||||
|
||||
## Invariants
|
||||
|
||||
- Prefer small, named modules with clear ownership.
|
||||
- Behavior changes need tests or an explicit eval.
|
||||
- Keep generated artifacts out of hand-written design docs.
|
||||
- Update this map when new top-level concepts or directories become important.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/design-docs/index.md",
|
||||
content: `# Design Docs
|
||||
|
||||
Durable design decisions live here. Link active proposals, completed decisions, and rejected alternatives.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/AGENTS.md",
|
||||
content: `# Docs Agent Notes
|
||||
|
||||
- Docs are the durable project memory. Keep them concise, navigable, and current.
|
||||
- Treat \`docs/adr/0000-purpose-to-software-compiler.md\` as the root SF product contract.
|
||||
- Put stable decisions here; keep transient execution state in active plans.
|
||||
- Prefer links to source paths, commands, and eval artifacts over broad prose.
|
||||
- When docs and code disagree, inspect the code and update the stale document.
|
||||
- Run the records keeper checklist in \`RECORDS_KEEPER.md\` after meaningful code, product, or architecture changes.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/records/AGENTS.md",
|
||||
content: `# Records Agent Notes
|
||||
|
||||
- Keep repository memory ordered, current, and easy to inspect.
|
||||
- Prefer moving durable facts to the narrowest canonical document over duplicating them.
|
||||
- Preserve historical decisions; mark superseded records instead of deleting useful context.
|
||||
- Escalate conflicts between docs and source by citing the exact files that disagree.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/records/index.md",
|
||||
content: `# Records
|
||||
|
||||
This folder holds repo-memory audits, decision ledgers, context-gardening notes, and records-keeper outputs.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/RECORDS_KEEPER.md",
|
||||
content: `# Records Keeper
|
||||
|
||||
The records keeper keeps repo memory ordered after meaningful changes. Run this checklist at milestone close, after architecture changes, after product behavior changes, and whenever docs/source disagree.
|
||||
|
||||
Use the \`records-keeper\` skill for this workflow when SF skills are available. Use \`context-doctor\` instead when stale state lives under \`.sf/\` or the memory store.
|
||||
|
||||
## Canonical Homes
|
||||
|
||||
- Root \`AGENTS.md\`: short routing map for agents.
|
||||
- \`ARCHITECTURE.md\`: short system map, boundaries, invariants, critical flows, and verification.
|
||||
- \`docs/product-specs/\`: durable user-facing behavior and product decisions.
|
||||
- \`docs/design-docs/\`: durable design and architecture decisions.
|
||||
- \`docs/exec-plans/\`: active/completed work plans and technical debt.
|
||||
- \`docs/generated/\`: generated references only.
|
||||
- \`docs/records/\`: audits, ledgers, and context-gardening outputs.
|
||||
|
||||
## Checklist
|
||||
|
||||
- Root map is current: \`AGENTS.md\` points to the right canonical docs and local \`AGENTS.md\` files.
|
||||
- Architecture is current: new subsystems, boundaries, invariants, data/state, or critical flows are reflected in \`ARCHITECTURE.md\`.
|
||||
- Product specs are current: user-visible behavior changes are reflected in \`docs/product-specs/\`.
|
||||
- Execution plans are filed: active work is in \`docs/exec-plans/active/\`; completed summaries and evidence are in \`docs/exec-plans/completed/\`.
|
||||
- Debt is visible: discovered cleanup is listed in \`docs/exec-plans/tech-debt-tracker.md\`.
|
||||
- Generated docs are marked: generated material stays under \`docs/generated/\` or clearly says how to regenerate it.
|
||||
- Contradictions are resolved: stale docs are updated or marked superseded with links to the source of truth.
|
||||
- Verification is recorded: changed checks, evals, and commands are listed in the relevant plan or quality document.
|
||||
|
||||
## Output
|
||||
|
||||
When records work is non-trivial, write a dated note under \`docs/records/\` with:
|
||||
|
||||
- What changed.
|
||||
- What canonical docs were updated.
|
||||
- What contradictions were found.
|
||||
- What remains unresolved.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/design-docs/AGENTS.md",
|
||||
content: `# Design Doc Agent Notes
|
||||
|
||||
- Capture problem, context, options, decision, consequences, and validation.
|
||||
- Separate observed facts from inferred product or architecture intent.
|
||||
- Record rejected alternatives when they would prevent repeated debate.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/design-docs/core-beliefs.md",
|
||||
content: `# Core Beliefs
|
||||
|
||||
- The repo should explain itself to humans and agents.
|
||||
- Plans should carry acceptance criteria, falsifiers, and verification commands.
|
||||
- Architecture should be mechanically checkable where possible.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/exec-plans/active/index.md",
|
||||
content: `# Active Execution Plans
|
||||
|
||||
Link active plans here. Each plan should state purpose, scope, tasks, acceptance criteria, and verification.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/exec-plans/AGENTS.md",
|
||||
content: `# Execution Plan Agent Notes
|
||||
|
||||
- Every plan needs purpose, scope, tasks, acceptance criteria, falsifier, and verification.
|
||||
- Active plans live in \`active/\`; completed evidence summaries live in \`completed/\`.
|
||||
- Add discovered cleanup to \`tech-debt-tracker.md\` instead of hiding it in chat.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/exec-plans/completed/index.md",
|
||||
content: `# Completed Execution Plans
|
||||
|
||||
Move finished plan summaries here with evidence links and follow-up debt.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/exec-plans/tech-debt-tracker.md",
|
||||
content: `# Tech Debt Tracker
|
||||
|
||||
Track cleanup discovered during implementation. Include owner, impact, proposed fix, and verification.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/generated/db-schema.md",
|
||||
content: `# Database Schema
|
||||
|
||||
Generated or refreshed schema notes belong here. Do not hand-maintain stale schema copies.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/product-specs/index.md",
|
||||
content: `# Product Specs
|
||||
|
||||
Durable user-facing behavior, workflows, and product decisions live here.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/product-specs/AGENTS.md",
|
||||
content: `# Product Spec Agent Notes
|
||||
|
||||
- Describe the user, job-to-be-done, workflow, edge cases, and non-goals.
|
||||
- Keep implementation details out unless they are product-visible constraints.
|
||||
- Update specs when behavior changes, especially onboarding, permissions, billing, or destructive actions.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/product-specs/new-user-onboarding.md",
|
||||
content: `# New User Onboarding
|
||||
|
||||
Describe the first-run experience, success criteria, and failure states when this product has an onboarding flow.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/references/design-system-reference-llms.txt",
|
||||
content: `Reference slot for design-system guidance intended for LLM consumption.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/references/nixpacks-llms.txt",
|
||||
content: `Reference slot for Nixpacks deployment/build guidance intended for LLM consumption.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/references/uv-llms.txt",
|
||||
content: `Reference slot for uv/Python tooling guidance intended for LLM consumption.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/DESIGN.md",
|
||||
content: `# Design
|
||||
|
||||
Record interaction patterns, visual constraints, and design-system usage here.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/FRONTEND.md",
|
||||
content: `# Frontend
|
||||
|
||||
Record frontend architecture, component ownership, accessibility constraints, and browser support here.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/PLANS.md",
|
||||
content: `# Plans
|
||||
|
||||
Use this as the index for current and upcoming work. Link detailed plans in \`docs/exec-plans/\`.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/PRODUCT_SENSE.md",
|
||||
content: `# Product Sense
|
||||
|
||||
Capture user goals, non-goals, tradeoffs, and examples of good product judgment for this repo.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/QUALITY_SCORE.md",
|
||||
content: `# Quality Score
|
||||
|
||||
Define what good looks like for this repo. Include fast checks, slow checks, evals, and known blind spots.
|
||||
|
||||
Use these principles:
|
||||
|
||||
- Make code legible to agents with semantic names and explicit boundaries.
|
||||
- Prefer small, testable modules over files that require broad context to edit.
|
||||
- Enforce style, architecture, and reliability rules mechanically where possible.
|
||||
- Keep a cleanup loop for stale docs, generated artifacts, and accumulated implementation debt.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/RELIABILITY.md",
|
||||
content: `# Reliability
|
||||
|
||||
Document expected failure modes, recovery paths, observability, and release checks here.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/SECURITY.md",
|
||||
content: `# Security
|
||||
|
||||
Document trust boundaries, secrets handling, dependency risk, and security review requirements here.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: "docs/design-docs/ADR-TEMPLATE.md",
|
||||
content: `# ADR-NNN: Title
|
||||
|
||||
**Status:** Proposed | Accepted | Rejected | Superseded by ADR-NNN
|
||||
**Date:** YYYY-MM-DD
|
||||
|
||||
## Context
|
||||
|
||||
What is the problem or situation that requires a decision? Include constraints and the forces at play.
|
||||
|
||||
## Decision
|
||||
|
||||
What is the change being made or the approach being adopted?
|
||||
|
||||
## Consequences
|
||||
|
||||
What becomes easier or harder after this decision? Include positive and negative outcomes.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
What other options were evaluated and why were they not chosen?
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/harness/AGENTS.md",
|
||||
content: `# Harness Agent Notes
|
||||
|
||||
The harness is SF-local operational scaffolding the agent can read and verify against.
|
||||
|
||||
- \`specs/\`: behavior contracts. Each spec states what "done" looks like and the command that proves it.
|
||||
- \`evals/\`: task definitions for behaviors tests cannot cover — model output quality, multi-turn flows, agent decisions.
|
||||
- \`graders/\`: reusable grader scripts (code-based checks, LLM-judge prompts used by evals).
|
||||
|
||||
**Rule:** Before marking a task done, run the relevant spec's verification command. Record the result in the completion summary or execution plan.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/harness/specs/AGENTS.md",
|
||||
content: `# Harness Specs Agent Notes
|
||||
|
||||
Each spec file in this directory:
|
||||
|
||||
- States the behavior being specified (not the implementation).
|
||||
- Includes the exact command that proves the spec passes.
|
||||
- Is referenced by the relevant execution plan or ADR.
|
||||
|
||||
Write the spec before implementation. Run it after. Record the result.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/harness/specs/bootstrap.md",
|
||||
content: `# Bootstrap Spec: Agent Legibility
|
||||
|
||||
Verifies that this repo is minimally agent-legible.
|
||||
|
||||
## Criteria
|
||||
|
||||
- [ ] \`AGENTS.md\` exists at repo root and is non-empty.
|
||||
- [ ] \`ARCHITECTURE.md\` exists at repo root and is non-empty.
|
||||
- [ ] \`docs/exec-plans/active/\` exists.
|
||||
- [ ] \`docs/exec-plans/tech-debt-tracker.md\` exists.
|
||||
- [ ] \`docs/design-docs/ADR-TEMPLATE.md\` exists.
|
||||
|
||||
## Verification command
|
||||
|
||||
\`\`\`bash
|
||||
for f in AGENTS.md ARCHITECTURE.md docs/exec-plans/active/index.md docs/exec-plans/tech-debt-tracker.md docs/design-docs/ADR-TEMPLATE.md .sf/harness/specs/bootstrap.md; do [ -s "$f" ] && echo "OK: $f" || echo "MISSING: $f"; done
|
||||
\`\`\`
|
||||
|
||||
All lines should start with \`OK:\` for the bootstrap spec to pass.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/harness/evals/AGENTS.md",
|
||||
content: `# Harness Evals Agent Notes
|
||||
|
||||
Evals verify behavior that unit tests cannot cover — model output quality, agent decisions, multi-turn flows.
|
||||
|
||||
Each eval should include:
|
||||
- The input fixture or prompt
|
||||
- The expected output or scoring rubric
|
||||
- The command to run it (\`promptfoo eval\`, custom script, etc.)
|
||||
|
||||
Keep evals deterministic where possible. Log results to \`docs/records/\` at milestone close.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/harness/graders/AGENTS.md",
|
||||
content: `# Harness Graders Agent Notes
|
||||
|
||||
Graders are reusable scripts or prompts that score eval outputs.
|
||||
|
||||
- Code-based graders: shell scripts or test files that check structured outputs deterministically.
|
||||
- LLM-judge graders: prompt templates that ask a model to score free-text output against a rubric.
|
||||
|
||||
Prefer code-based graders. Add LLM-judge graders only when deterministic checking is impossible.
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/PRINCIPLES.md",
|
||||
content: `# Principles
|
||||
|
||||
Durable design philosophy. Things this codebase believes are true.
|
||||
|
||||
Add entries as you make decisions. Each entry: 1-2 sentences. Cite the rationale (the why, not just the what).
|
||||
|
||||
## Examples
|
||||
|
||||
- (replace with your own)
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/TASTE.md",
|
||||
content: `# Taste
|
||||
|
||||
What good code looks like here. Idioms, conventions, "we prefer X over Y" calls.
|
||||
|
||||
Add entries as you notice patterns worth preserving. Each entry: 1-2 sentences with a concrete example.
|
||||
|
||||
## Examples
|
||||
|
||||
- (replace with your own)
|
||||
`,
|
||||
},
|
||||
{
|
||||
path: ".sf/ANTI-GOALS.md",
|
||||
content: `# Anti-goals
|
||||
|
||||
What we explicitly DON'T want. Things that look attractive but we've decided against.
|
||||
|
||||
This is gold — most wrong agent calls come from not knowing what to avoid. Each entry: 1-2 sentences with the rationale.
|
||||
|
||||
## Examples
|
||||
|
||||
- (replace with your own)
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
*/
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { SCAFFOLD_FILES } from "./agentic-docs-scaffold.js";
|
||||
import { SCAFFOLD_FILES } from "./scaffold-constants.js";
|
||||
import {
|
||||
bodyHash,
|
||||
extractMarker,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import {
|
|||
} from "./native-git-bridge.js";
|
||||
import { nativeParseJsonlTail } from "./native-parser-bridge.js";
|
||||
import { sfRuntimeRoot } from "./paths.js";
|
||||
import { getActiveMilestoneIdFromDb } from "./sf-db.js";
|
||||
|
||||
// ─── JSONL Parsing ────────────────────────────────────────────────────────────
|
||||
// MAX_JSONL_BYTES and parseJSONL are imported from ./jsonl-utils.js
|
||||
|
|
@ -265,16 +266,16 @@ export function getDeepDiagnostic(basePath, worktreePath) {
|
|||
return formatTraceSummary(trace);
|
||||
}
|
||||
/**
|
||||
* Read the active milestone ID directly from STATE.md without async deriveState().
|
||||
* Looks for `**Active Milestone:** M001` pattern.
|
||||
* Read the active milestone ID from the DB.
|
||||
*
|
||||
* Purpose: provide a sync-compatible active milestone lookup without reading
|
||||
* the STATE.md cache, which can be stale between DB writes and file flushes.
|
||||
* Consumer: auto.js getDeepDiagnostic, crash recovery briefing.
|
||||
*/
|
||||
export function readActiveMilestoneId(basePath) {
|
||||
export function readActiveMilestoneId(_basePath) {
|
||||
try {
|
||||
const statePath = join(sfRuntimeRoot(basePath), "STATE.md");
|
||||
if (!existsSync(statePath)) return null;
|
||||
const content = readFileSync(statePath, "utf-8");
|
||||
const match = /\*\*Active Milestone:\*\*\s*(\S+)/i.exec(content);
|
||||
return match?.[1] ?? null;
|
||||
const result = getActiveMilestoneIdFromDb();
|
||||
return result?.id ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import {
|
|||
resolveTaskFile,
|
||||
sfRoot,
|
||||
} from "../paths.js";
|
||||
import { getSlice, isDbAvailable } from "../sf-db.js";
|
||||
import { getSlice, getTask, isDbAvailable } from "../sf-db.js";
|
||||
import { parseUnitId } from "../unit-id.js";
|
||||
import {
|
||||
normalizeUnitLineage,
|
||||
|
|
@ -474,21 +474,19 @@ export async function inspectExecuteTaskDurability(basePath, unitId) {
|
|||
if (!mid || !sid || !tid) return null;
|
||||
const planAbs = resolveSliceFile(basePath, mid, sid, "PLAN");
|
||||
const summaryAbs = resolveTaskFile(basePath, mid, sid, tid, "SUMMARY");
|
||||
const stateAbs = join(sfRoot(basePath), "STATE.md");
|
||||
const planPath = relSliceFile(basePath, mid, sid, "PLAN");
|
||||
const summaryPath = relTaskFile(basePath, mid, sid, tid, "SUMMARY");
|
||||
const planContent = planAbs ? await loadFile(planAbs) : null;
|
||||
const stateContent = existsSync(stateAbs)
|
||||
? readFileSync(stateAbs, "utf-8")
|
||||
: "";
|
||||
const summaryExists = !!(summaryAbs && existsSync(summaryAbs));
|
||||
const escapedTid = tid.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const taskChecked =
|
||||
!!planContent &&
|
||||
new RegExp(`^- \\[[xX]\\] \\*\\*${escapedTid}:`, "m").test(planContent);
|
||||
const nextActionAdvanced = !new RegExp(`Execute ${tid}\\b`).test(
|
||||
stateContent,
|
||||
);
|
||||
// Use DB task status instead of reading STATE.md — STATE.md is a display
|
||||
// projection and may lag behind DB writes by a loop iteration.
|
||||
const taskRow = isDbAvailable() ? getTask(mid, sid, tid) : null;
|
||||
const taskStatus = taskRow?.status ?? "pending";
|
||||
const nextActionAdvanced = taskStatus !== "pending" && taskStatus !== "in_progress";
|
||||
// Must-have coverage: load task plan and count mentions in summary
|
||||
let mustHaveCount = 0;
|
||||
let mustHavesMentionedInSummary = 0;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { existsSync, readFileSync, statSync } from "node:fs";
|
|||
import { join } from "node:path";
|
||||
import { countPendingCaptures, loadAllCaptures } from "./captures.js";
|
||||
import { runEnvironmentChecks } from "./doctor-environment.js";
|
||||
import { getHealthHistory } from "./doctor-proactive.js";
|
||||
import { runProviderChecks } from "./doctor-providers.js";
|
||||
import { loadFile, parseSummary } from "./files.js";
|
||||
import {
|
||||
|
|
@ -25,7 +24,6 @@ import {
|
|||
sfRoot,
|
||||
} from "./paths.js";
|
||||
import { loadEffectiveSFPreferences } from "./preferences.js";
|
||||
import { computeProgressScore } from "./progress-score.js";
|
||||
import { getMilestoneSlices, getSliceTasks, isDbAvailable } from "./sf-db.js";
|
||||
import { generateSkillHealthReport } from "./skill-health.js";
|
||||
import { deriveState } from "./state.js";
|
||||
|
|
@ -355,7 +353,7 @@ function loadKnowledge(basePath) {
|
|||
return { rules, patterns, lessons, exists: true };
|
||||
}
|
||||
// ─── Health Loader ────────────────────────────────────────────────────────────
|
||||
function loadHealth(units, totals, basePath) {
|
||||
async function loadHealth(units, totals, basePath) {
|
||||
const prefs = loadEffectiveSFPreferences();
|
||||
const budgetCeiling = prefs?.preferences?.budget_ceiling;
|
||||
const tokenProfile = prefs?.preferences?.token_profile ?? "standard";
|
||||
|
|
@ -440,8 +438,10 @@ function loadHealth(units, totals, basePath) {
|
|||
// Current progress score — only meaningful when autonomous mode has health data
|
||||
let progressScore = null;
|
||||
try {
|
||||
const { getHealthHistory } = await import("./doctor-proactive.js");
|
||||
const history = getHealthHistory();
|
||||
if (history.length > 0) {
|
||||
const { computeProgressScore } = await import("./progress-score.js");
|
||||
const score = computeProgressScore();
|
||||
progressScore = {
|
||||
level: score.level,
|
||||
|
|
@ -704,7 +704,7 @@ export async function loadVisualizerData(basePath) {
|
|||
pendingCount,
|
||||
totalCount: allCaptures.length,
|
||||
};
|
||||
const health = loadHealth(units, totals, basePath);
|
||||
const health = await loadHealth(units, totals, basePath);
|
||||
const stats = buildVisualizerStats(milestones, changelog.entries);
|
||||
const discussion = loadDiscussionState(basePath, milestones);
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -26,8 +26,7 @@ import {
|
|||
import { join } from "node:path";
|
||||
import { withFileLockSync } from "./file-lock.js";
|
||||
import { appendNotification } from "./notification-store.js";
|
||||
import { buildAuditEnvelope, emitUokAuditEvent } from "./uok/audit.js";
|
||||
import { isAuditEnvelopeEnabled } from "./uok/audit-toggle.js";
|
||||
|
||||
|
||||
// ─── Buffer & Persistent Audit ──────────────────────────────────────────
|
||||
const MAX_BUFFER = 100;
|
||||
|
|
@ -230,32 +229,10 @@ function _push(severity, component, message, context) {
|
|||
if (_buffer.length > MAX_BUFFER) {
|
||||
_buffer.shift();
|
||||
}
|
||||
if (_auditBasePath && isAuditEnvelopeEnabled()) {
|
||||
try {
|
||||
emitUokAuditEvent(
|
||||
_auditBasePath,
|
||||
buildAuditEnvelope({
|
||||
traceId: `workflow-log:${component}`,
|
||||
turnId: context?.id,
|
||||
causedBy: context?.fn ?? context?.tool,
|
||||
category: "orchestration",
|
||||
type:
|
||||
severity === "error" ? "workflow-log-error" : "workflow-log-warn",
|
||||
payload: {
|
||||
component,
|
||||
message,
|
||||
context: context ?? {},
|
||||
},
|
||||
}),
|
||||
);
|
||||
} catch (auditEmitErr) {
|
||||
// Best-effort: unified audit projection must never block workflow logger.
|
||||
// Increment failure counter so doctor/status can surface persistent divergence.
|
||||
_auditEmitFailureCount++;
|
||||
_writeStderr(
|
||||
`[sf:workflow-logger] unified-audit emit failed: ${auditEmitErr.message}\n`,
|
||||
);
|
||||
}
|
||||
if (_auditBasePath) {
|
||||
// Fire-and-forget: audit emission is best-effort and must not block the sync logger.
|
||||
// Dynamic imports break the sf-db → workflow-logger → uok/audit → sf-db cycle.
|
||||
_tryEmitAuditEvent(_auditBasePath, severity, component, message, context);
|
||||
}
|
||||
// Persist errors to .sf/audit-log.jsonl so they survive context resets.
|
||||
// Only error-severity entries are persisted — warnings are ephemeral (stderr + buffer)
|
||||
|
|
@ -284,6 +261,33 @@ function _push(severity, component, message, context) {
|
|||
}
|
||||
}
|
||||
}
|
||||
async function _tryEmitAuditEvent(basePath, severity, component, message, context) {
|
||||
try {
|
||||
const { isAuditEnvelopeEnabled } = await import("./uok/audit-toggle.js");
|
||||
if (!isAuditEnvelopeEnabled()) return;
|
||||
const { buildAuditEnvelope, emitUokAuditEvent } = await import("./uok/audit.js");
|
||||
emitUokAuditEvent(
|
||||
basePath,
|
||||
buildAuditEnvelope({
|
||||
traceId: `workflow-log:${component}`,
|
||||
turnId: context?.id,
|
||||
causedBy: context?.fn ?? context?.tool,
|
||||
category: "orchestration",
|
||||
type: severity === "error" ? "workflow-log-error" : "workflow-log-warn",
|
||||
payload: {
|
||||
component,
|
||||
message,
|
||||
context: context ?? {},
|
||||
},
|
||||
}),
|
||||
);
|
||||
} catch (auditEmitErr) {
|
||||
_auditEmitFailureCount++;
|
||||
_writeStderr(
|
||||
`[sf:workflow-logger] unified-audit emit failed: ${auditEmitErr.message}\n`,
|
||||
);
|
||||
}
|
||||
}
|
||||
function _writeStderr(message) {
|
||||
if (!_stderrEnabled) return;
|
||||
process.stderr.write(message);
|
||||
|
|
|
|||
57
src/resources/extensions/sf/worktree-detect.js
Normal file
57
src/resources/extensions/sf/worktree-detect.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* Thin worktree-detection helpers with no intra-repo imports.
|
||||
*
|
||||
* Extracted from worktree.js so that git-service.js can import
|
||||
* detectWorktreeName without pulling in the full worktree module
|
||||
* (which in turn imports GitServiceImpl from git-service.js, creating
|
||||
* a cycle). worktree.js re-exports both symbols for back-compat.
|
||||
*
|
||||
* Purpose: provide a zero-dependency path for detecting whether a
|
||||
* basePath lives inside a SF worktree (.sf/worktrees/<name>).
|
||||
*/
|
||||
|
||||
/**
|
||||
* Locate the worktree segment within a normalized path string.
|
||||
* Returns the string indices needed by detectWorktreeName and
|
||||
* resolveProjectRoot; returns null if not inside a SF worktree.
|
||||
*
|
||||
* @param {string} normalizedPath — forward-slash-normalised absolute path.
|
||||
*/
|
||||
function findWorktreeSegment(normalizedPath) {
|
||||
// Direct layout: /.sf/worktrees/<name>
|
||||
const directMarker = "/.sf/worktrees/";
|
||||
const idx = normalizedPath.indexOf(directMarker);
|
||||
if (idx !== -1) {
|
||||
return { sfIdx: idx, afterWorktrees: idx + directMarker.length };
|
||||
}
|
||||
// Symlink-resolved layout: /.sf/projects/<hash>/worktrees/<name>
|
||||
const symlinkRe = /\/\.sf\/projects\/[a-f0-9]+\/worktrees\//;
|
||||
const match = normalizedPath.match(symlinkRe);
|
||||
if (match && match.index !== undefined) {
|
||||
return {
|
||||
sfIdx: match.index,
|
||||
afterWorktrees: match.index + match[0].length,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the active worktree name from the current working directory.
|
||||
* Returns null if not inside a SF worktree (.sf/worktrees/<name>/).
|
||||
*
|
||||
* Consumer: git-service.js (GitServiceImpl.getTargetBranch) and worktree.js.
|
||||
*
|
||||
* @param {string} basePath
|
||||
* @returns {string | null}
|
||||
*/
|
||||
export function detectWorktreeName(basePath) {
|
||||
const normalizedPath = basePath.replaceAll("\\", "/");
|
||||
const seg = findWorktreeSegment(normalizedPath);
|
||||
if (!seg) return null;
|
||||
const afterMarker = normalizedPath.slice(seg.afterWorktrees);
|
||||
const name = afterMarker.split("/")[0];
|
||||
return name || null;
|
||||
}
|
||||
|
||||
export { findWorktreeSegment };
|
||||
|
|
@ -16,8 +16,14 @@ import { homedir } from "node:os";
|
|||
import { join, resolve } from "node:path";
|
||||
import { GitServiceImpl, writeIntegrationBranch } from "./git-service.js";
|
||||
import { loadEffectiveSFPreferences } from "./preferences.js";
|
||||
import {
|
||||
detectWorktreeName,
|
||||
findWorktreeSegment,
|
||||
} from "./worktree-detect.js";
|
||||
|
||||
export { MergeConflictError } from "./git-service.js";
|
||||
// Re-export for consumers that import detectWorktreeName from ./worktree.js
|
||||
export { detectWorktreeName } from "./worktree-detect.js";
|
||||
|
||||
// ─── Lazy GitServiceImpl Cache ─────────────────────────────────────────────
|
||||
let cachedService = null;
|
||||
|
|
@ -68,43 +74,6 @@ export function captureIntegrationBranch(basePath, milestoneId) {
|
|||
writeIntegrationBranch(basePath, milestoneId, current);
|
||||
}
|
||||
// ─── Pure Utility Functions (unchanged) ────────────────────────────────────
|
||||
/**
|
||||
* Find the worktrees segment in a path, supporting both direct
|
||||
* (`/.sf/worktrees/`) and symlink-resolved (`/.sf/projects/<hash>/worktrees/`)
|
||||
* layouts. When `.sf` is a symlink to `~/.sf/projects/<hash>`, resolved
|
||||
* paths contain the intermediate `projects/<hash>/` segment that the old
|
||||
* single-marker check missed.
|
||||
*/
|
||||
function findWorktreeSegment(normalizedPath) {
|
||||
// Direct layout: /.sf/worktrees/<name>
|
||||
const directMarker = "/.sf/worktrees/";
|
||||
const idx = normalizedPath.indexOf(directMarker);
|
||||
if (idx !== -1) {
|
||||
return { sfIdx: idx, afterWorktrees: idx + directMarker.length };
|
||||
}
|
||||
// Symlink-resolved layout: /.sf/projects/<hash>/worktrees/<name>
|
||||
const symlinkRe = /\/\.sf\/projects\/[a-f0-9]+\/worktrees\//;
|
||||
const match = normalizedPath.match(symlinkRe);
|
||||
if (match && match.index !== undefined) {
|
||||
return {
|
||||
sfIdx: match.index,
|
||||
afterWorktrees: match.index + match[0].length,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Detect the active worktree name from the current working directory.
|
||||
* Returns null if not inside a SF worktree (.sf/worktrees/<name>/).
|
||||
*/
|
||||
export function detectWorktreeName(basePath) {
|
||||
const normalizedPath = basePath.replaceAll("\\", "/");
|
||||
const seg = findWorktreeSegment(normalizedPath);
|
||||
if (!seg) return null;
|
||||
const afterMarker = normalizedPath.slice(seg.afterWorktrees);
|
||||
const name = afterMarker.split("/")[0];
|
||||
return name || null;
|
||||
}
|
||||
/**
|
||||
* Resolve the project root from a path that may be inside a worktree.
|
||||
* If the path contains a worktrees segment, returns the portion before
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue