The 'read SUMMARY → check if readable AND terminal' pattern appeared five
times in state.js after the Cluster F polarity fix. Extract it to a
private loadTerminalSummary(summaryFile, loadFn) helper so the fail-closed
semantics live in one place and can't drift between call sites.
- loadTerminalSummary returns the content if readable AND terminal, null otherwise
- All 5 call sites replaced: 2 in getActiveMilestoneId(), 3 in _deriveStateImpl()
- Phase 2 'no roadmap' case reuses returned content for parseSummary().title
- isTerminalMilestoneSummaryContent now only referenced inside the helper
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
No interface exists for the class, so the Impl suffix is vestigial
Java-style naming. Rename throughout: git-service.js, auto-start.js,
auto.js, worktree.js, worktree-detect.js, worktree-resolver.js,
quick.js, and the two test files that imported it directly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Three fail-open bugs allowed unreadable (null) SUMMARY files to be treated as
terminal, incorrectly marking milestones as complete when the content could not
be read.
Gap 1 — dispatch-guard.js line 50:
Any SUMMARY file existence = milestone complete (fail-open).
Fix: DB-first check via getMilestone()+isClosedStatus(); filesystem fallback
reads SUMMARY content and calls classifyMilestoneSummaryContent() so only
non-failure summaries skip the milestone.
Gap 2 — state.js getActiveMilestoneId():
'if (summaryFile) continue' skipped any milestone with ANY SUMMARY.
'if (!summaryFile) return mid' fell through incorrectly for failure SUMMARYs.
Fix: read content; only skip/continue if sc != null && isTerminal(sc).
Gap 3 — state.js _deriveStateImpl() Phase 1 + Phase 2:
'!sc || isTerminalMilestoneSummaryContent(sc)' — null content = fail-open.
Fix: 'sc && isTerminalMilestoneSummaryContent(sc)' — null content = fail-closed.
Applied to all 6 occurrences (lines 1233, 1247, 1257, 1284, 1356, 1391).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fold sf-usage-bar, sf-notify, sf-inturn-guard, sf-permissions,
slash-commands into sf extension (ui/, notifications/, guards/,
permissions/, commands/legacy/)
- Delete vectordrive extension
- Migrate uok/kernel.js to TypeScript (kernel.ts) with full interfaces
- Add allowJs/checkJs:false to tsconfig.resources.json for incremental TS migration
- Add symlink dedup to extension-discovery.ts (seenRealPaths Set)
- Add before_provider_request delegate back to native-search.js so
session budget tests exercise the middleware end-to-end
- Fix parseSfNativeTools() to return all SF manifest tools (drop sf_ filter)
- Fix test assertions: plan_milestone/complete_task/validate_milestone
- Remove subagent from app-smoke.test.ts (folded into sf/subagent/)
- Remove sf-permissions/sf-inturn-guard/subagent from features-inventory test
- Fix resolveSearchProvider autonomous mode test to pass 'auto' explicitly
- Remove legacy /clear slash command (conflicts with built-in clear_terminal)
- Update web-command-parity-contract.test.ts for clear removal
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- preferences-models.js: replace 6-regex isHeavyModelId() with MODEL_CAPABILITY_TIER
lookup + regex fallback for unknown models; new models in model-router.js
are automatically reflected without touching preferences-models.js
- search-the-web/provider.js: replace ~200-line per-provider waterfall with
PROVIDER_REGISTRY array + firstAvailable()/resolveWithFallback() helpers;
preserves Tavily→Brave→Serper→Exa→Ollama→MiniMax auto-fallback order
- sf-db.js: bump SCHEMA_VERSION 58→60 (v59 now reachable); add
frontmatter_version column to tasks table via v60 migration and CREATE
TABLE definition; wire frontmatter_version into upsertTaskPlanning() SQL
and .run() params
- task-frontmatter.js: add frontmatterVersion:1 to DEFAULT_TASK_FRONTMATTER,
add validation block in validateTaskFrontmatter(), add frontmatterVersion
mapping in taskFrontmatterFromRecord()
- sf-db-migration.test.mjs: update hardcoded version assertion 58→60
- docs/specs/sf-operating-model.md: add Planning Schema section documenting
the 3-table model (milestones/slices/tasks, their PKs, spec tables, and
ID naming conventions)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- FallbackResolver.setUnitContext() stores {unitType,unitId} from autonomous dispatch
- run-unit.js calls pi.setFallbackUnitContext() before/after each unit
- _findAnyAvailableFallback uses real unitType/unitId from context, not sentinel
- Schema v59: failure_mode column in llm_task_outcomes
- insertLlmTaskOutcome accepts failure_mode (rate_limit, quota_exhausted, auth_error)
- register-hooks.js passes event.classification.reason as failure_mode
- register-hooks.js uses real event.unitId when available
- ExtensionRuntimeActions.setFallbackUnitContext added to pi API surface
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When a model fails and FallbackResolver picks a replacement, it now:
1. Fires the before_model_select hook with reason='fallback' and the
failing model's ID — the learning system records the failure outcome
and returns the best Bayesian-blended replacement from llm_task_outcomes
2. Falls back to the existing heuristic sort (reasoning + context window)
if the hook is unavailable or returns no override
Changes:
- BeforeModelSelectEvent: add optional currentModelId and reason fields
- FallbackResolver: accept emitBeforeModelSelect in constructor; make
_findAnyAvailableFallback async; fire hook before heuristic fallback
- agent-session.ts: inject lazy emitBeforeModelSelect closure into resolver
- register-hooks.js: record failure outcome when reason='fallback' before
returning selectLearnedModel result
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add packages/coding-agent/src/utils/format.ts as the canonical source
for formatDuration, formatTokenCount, truncateWithEllipsis, sparkline,
formatDateShort, fileLink, stripAnsi, normalizeStringArray — all already
exported from @singularity-forge/coding-agent via index.ts.
- Convert shared/format-utils.js to a compatibility shim that re-exports
the 8 functions from @singularity-forge/coding-agent. All 13 importers
continue to work with no import changes required.
- Convert shared/path-display.js to a compatibility shim that re-exports
toPosixPath from @singularity-forge/coding-agent. Implementation in
packages/coding-agent/src/utils/path-display.ts was already canonical.
- shared/frontmatter.js is intentionally NOT shimmed: splitFrontmatter/
parseFrontmatterMap have a different API from the package's parseFrontmatter/
stripFrontmatter (flat-map vs {frontmatter, body} object).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Create packages/coding-agent/src/core/providers/web-search-middleware.ts with
WebSearchMiddleware class: injects web_search tool, enforces session budget (#1309),
strips thinking blocks from history, and respects PREFERENCES.md search_provider.
- Wire webSearchMiddleware.applyToPayload into sdk.ts onPayload callback (before
extension hook dispatch) so injection runs as compiled TypeScript with zero
jiti-dispatch overhead.
- Export WebSearchMiddleware, webSearchMiddleware singleton, setPreferBraveResolver,
CUSTOM_SEARCH_TOOL_NAMES, MAX_NATIVE_SEARCHES_PER_SESSION, and stripThinkingFromHistory
from @singularity-forge/coding-agent so the extension can delegate to the same instance.
- Refactor search-the-web/native-search.js: remove self-contained injection logic;
import and delegate before_provider_request to webSearchMiddleware singleton.
Use tri-state isAnthropicProvider (null/false/true) to synthesize a provider hint
when event.model is absent but model_select has already fired — prevents the
model-name heuristic from wrongly injecting into Copilot claude-* requests.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Install @a2a-js/sdk v0.3.13 as a dependency
- Add a2a-transport.js: A2ATransport class with spawnAgent, dispatch,
getOrSpawnAgent, and buildAgentCard; spawns pi subprocesses with
SF_A2A_AGENT_* env vars and dispatches envelopes via A2A JSON-RPC
- Add a2a-agent-server.js: A2A HTTP server entrypoint for spawned agent
processes; starts express + A2AExpressApp with DefaultRequestHandler,
handles incoming DispatchEnvelopes via SwarmAgentExecutor, writes
envelope to SQLite MessageBus, and signals readiness via stdout JSON
- Update swarm-dispatch.js: split dispatch() into _busDispatch()
(existing SQLite path) and _a2aDispatch() (new A2A path); lazy-load
A2ATransport singleton only when SF_A2A_ENABLED is set; default
path unchanged for all existing callers
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Create config.ts with McpServerConfig types and readMcpConfigs/getServerConfig
- Create auth.ts with buildHttpTransportOpts and createCliOAuthProvider
- Create connection-manager.ts with McpConnectionManager class
- Create index.ts re-exporting the public API
- Export McpConnectionManager and helpers from @singularity-forge/coding-agent
- Rewrite mcp-client extension as thin wrapper using McpConnectionManager
- Rewrite auth.js as re-export shim from @singularity-forge/coding-agent
- Update test to import buildHttpTransportOpts from @singularity-forge/coding-agent
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
sf-tui was a 'bundled' extension with zero features independent of the sf/
extension. Every hook, shortcut, tool, header and footer render depended
on sf/ internals (getAutoSession, isAutoActive, projectRoot,
getExperimentalFlag). The separation was artificial.
Changes:
- Moved all sf-tui/*.js into sf/ui/ (header, footer, git, color-band, emoji,
prompt-history, marketplace, powerline, shared)
- Fixed imports: ../sf/ → ../ (one level up from ui/)
- Registered sf/ui/index.js from sf/index.js in a try/catch so a UI failure
can't take out the core SF commands
- Merged sf-tui manifest entries (9 commands, 3 shortcuts, agent_start hook)
into sf/extension-manifest.json
- Deleted src/resources/extensions/sf-tui/ entirely
- Fixed prompt-history.test.mjs import path
Result: one fewer extension to discover, load and validate at startup.
sf is now the single extension that owns both planning state and UI chrome.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Ink bridge added today was a misguided gradual-migration wrapper:
- Components still rendered via the old string-line protocol (no Ink layout)
- Key decodes were re-encoded to escape sequences → keys.ts decoded again (double round-trip bug)
- The _useInk / _inkHandle path blocked TTY start unconditionally via process.stdout.isTTY check
Removed: ink-bridge.tsx, ink-bridge.test.ts, useInk() method, _useInk/_inkHandle fields,
startInkRenderer import/export, Ink branch in start()/stop()/requestRender().
Removed ink and react from packages/tui dependencies and peerDependencies.
Reverted tsconfig.extensions.json jsx settings (only needed for the .tsx bridge file).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
tsgo (TS7 native port) requires explicit jsx setting when .tsx files are
in scope. tsc 6 was lenient; tsgo errors without it.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
New installations create .sf/preferences.yaml (pure YAML, no frontmatter
markers) and ~/.sf/preferences.yaml. Existing .md files are read as fallbacks
with no migration required for current users.
Changes:
- preferences.js: add yaml path getters, load chain tries .yaml first, add
parsePreferencesYaml() for direct YAML parse without frontmatter extraction
- templates/preferences.yaml: new canonical template (pure YAML with comment
header pointing to preferences-reference.md)
- gitignore.js: ensurePreferences() creates preferences.yaml; simplified by
removing scaffold-versioning dependency
- init-wizard.js: buildPreferencesFile() produces pure YAML, writes preferences.yaml
- commands-prefs-wizard.js: savePreferencesFile() helper handles .yaml vs .md;
ensurePreferencesFile uses yaml template for yaml paths
- preferences-template-upgrade.js: yaml files get raw YAML on upgrade
- planning-depth.js: returns {path, isYaml}, handles both formats
- deep-project-setup-policy.js: isWorkflowPrefsCaptured() tries all 3 paths
- detection.js: preferences.yaml added to all detection checks
- auto-worktree.js: canonical=yaml, LEGACY_PREFERENCES_FILES=["PREFERENCES.md","preferences.md"]
- auto-bootstrap-context.js: preferences.yaml before PREFERENCES.md in list
- guided-flow.js / worktree-root.js: existence checks include preferences.yaml
- User-visible strings / comments updated throughout
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Prompts: replace 'append to .sf/DECISIONS.md' → 'call save_decision' in
plan-slice, heal-skill (KNOWLEDGE.md), refine-slice, queue, guided-execute-task
- Prompts: replace 'Read .sf/DECISIONS.md if it exists' / 'Read .sf/REQUIREMENTS.md if it exists'
with 'injected from DB into system context' in guided-plan-slice, guided-research-slice
- requirement-promoter: remove dead appendRequirementRow() and readHighestRNumber(file)
that read/wrote REQUIREMENTS.md; replace with DB-only readHighestRNumber() using
getActiveRequirements(); remove sfRoot import, mkdirSync, writeFileSync
- requirement-promoter: pre-compute highestNum once per sweep loop instead of
re-reading for each cluster (fixes ID collision when promoting multiple at once)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- auto.js, auto/loop.js, bootstrap/register-hooks.js: tag all
autonomous-mode system notices with NOTICE_KIND.SYSTEM_NOTICE;
add dedupe_key to loop-level model-policy and flow-audit notices
- web/notifications-service.ts: add repeatCount/lastTs/noticeKind to
Notification type (schema v2 fields)
- uok/trace-writer.js: new unit trace writer
- tests/notification-store-grouping.test.mjs: grouping test coverage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The function and node:fs/os/path imports were dropped from the source
during editing. Added them back. Updated memory-embeddings-llm-gateway
test to cover auth.json-only behavior (no env var aliases).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Gateway key and URL are now read exclusively from ~/.sf/agent/auth.json
under the 'llm-gateway' entry. Removed env var support for the API key
(SF_LLM_GATEWAY_KEY, LLM_MUX_API_KEY, etc.) — credentials belong in
auth.json alongside all other provider keys, not in the environment.
Model/instruction overrides (SF_LLM_GATEWAY_EMBED_MODEL etc.) still
read from env vars as they are tuning knobs, not secrets.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Delete memory-backfill.js — not imported anywhere, dead code
- Rename memory-sleeper.js → tool-watchdog.js — misnamed; it is a
tool-output watchdog with no relation to the memory store
- Collapse memory-embeddings-llm-gateway.js into memory-embeddings.js —
removes the lazy-import split; loadGatewayConfigFromEnv,
createGatewayEmbedFn, and rerankCandidates are now direct exports
- Remove buildEmbeddingFn() dead stub (always returned null)
- Enable packages/coding-agent memory extraction extension by default
(memory.enabled ?? true) so session-level extraction is active
- Update all import sites and tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Local SQLite is the memory system. External Singularity Memory is an
optional cross-project enhancement, not a dependency. Flip the default
so SM is disabled unless explicitly opted in via SM_ENABLED=true:
- sm-client.js: return disconnected early unless SM_ENABLED=true
- memory-store.js: only pass smConnected=true when SM_ENABLED=true
- doctor-config-checks.js: skip SM health check when not opted in
- sm-client.test.ts: update test to reflect opt-in behaviour
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- knowledge-compounding.js: replace KNOWLEDGE.md file-read dedup with
getActiveMemories() DB query; file was never written so dedup was
always empty, causing duplicates to accumulate on every milestone close
- knowledge-compounding.js + save_knowledge tool: map confidence strings
('high'/'medium'/'low') to numeric scores (0.9/0.6/0.3) for the
memories.confidence REAL column; string values coerced to 0.0 by
SQLite, silently making all knowledge entries rank last and never
appear in system context
- save_knowledge: use K-${randomUUID()} (full UUID) instead of
K-${randomUUID().slice(0,8)} to match knowledge-compounding.js and
avoid collision risk
- complete-milestone.md: replace '.sf/DECISIONS.md' file reference with
'decisions inlined from DB' — the file is not generated anymore
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Verifies the function handles null/undefined content gracefully and
correctly extracts goal, demo, verification, and observability sections
from slice plan content. Addresses sf-mozutl5d-ei3ec6 by ensuring the
function is importable and behaves correctly end-to-end.
- doRender() now catches render errors and emits a fallback line
- autonomousStatus ANSI formatting extracted to renderAutonomousStatus()
with named color constants instead of raw escape strings
- parseCellSizeResponse extracted to pure function with proper validation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- TUI.useInk() opts into Ink-backed rendering (call before start())
- In start(): if _useInk || process.stdout.isTTY, mount Ink renderer via
startInkRenderer() and skip the legacy differential render path entirely
- In stop(): unmount Ink handle and return early; legacy terminal cleanup
(cursor repositioning, showCursor, terminal.stop) is skipped since Ink
handles terminal restoration itself
- Passes this.render()/invalidate() via a plain Component wrapper to avoid
the private handleInput TypeScript conflict
- Two new contract tests: useInk() flag and stop() Ink handle teardown
- 80/80 tests pass; legacy path unchanged for non-TTY (CI/tests)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Install ink@7.0.2 + react@19.2.6. Add JSX/react-jsx support to
packages/tui tsconfig. Create ink-bridge.tsx: LegacyComponentView wraps
existing Component objects as React nodes, startInkRenderer drives the
Ink render loop around any legacy Component tree.
Exports startInkRenderer from @singularity-forge/tui public API.
All 78 existing tui tests pass; 3 new ink-bridge tests added.
This is the infrastructure step for migrating components one-by-one from
the custom differential renderer to native Ink React components, without
breaking interactive mode.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove vestigial experimentalDecorators/emitDecoratorMetadata from all
package tsconfigs (no actual decorators in source — flags were from
pi-mono vendor copy)
- Add @typescript/native-preview for 8-10x faster type checking (measured
4.6x on this repo: tsc 6.5s vs tsgo 1.4s)
- Fix tsconfig.extensions.json: remove baseUrl (removed in tsgo/TS7) and
use relative paths in paths mappings — compatible with both tsc and tsgo
- Add typecheck/typecheck:extensions scripts using tsgo
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>