test(engine): repoint source-scan contracts to the peeled module layout
Full-suite repair, engine source-scan cluster. The package code organization waves moved ~30 engine modules into subdirectories (plugins/, execution/, scheduling/, healing/, worktree/, executor/ peels); the log-severity manifest, prompt carve-out, emit-surface, failure-lane, and worktree-invariant scanners now read the moved locations, verified per file via git log --follow. Two scans caught real drift rather than moves: the lifecycle census had 12 unexamined column guards (resolved with DELIBERATE-LITERAL markers for the mailbox archived tab, the FN-9059 lease-owner terminality check, and the FN-9056 legacy done fallback — baseline re-recorded with zero absorbed debt), and planning-claim gained a genuine second writer in self-healing's FN-8998 transport-failure recovery, admitted to the allowlist with its CAS-guarded justification. 9 files / 119 tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,12 @@ export interface WorkspaceLandIntent { taskId: string; repoRelPath: string; remo
|
||||
* FNXC:Workspace 2026-08-15-08:23:
|
||||
* Store reclaim and workspace self-healing share this deliberately narrow
|
||||
* terminal rule so either cannot reclaim a task the other considers live.
|
||||
*
|
||||
* DELIBERATE-LITERAL — narrow terminal-owner rule (FN-9059). A lease-owner row is
|
||||
* read without its workflow context, so resolving the complete lane per-workflow
|
||||
* here would let a resolver failure make a live owner read as terminal and allow a
|
||||
* competing reclaim. The legacy `done` literal is the intentionally conservative
|
||||
* shared floor for both reclaim paths.
|
||||
*/
|
||||
export function isTerminalWorkspaceLeaseOwner(row: Pick<Task, "column" | "status"> | null | undefined): boolean {
|
||||
return row != null && (row.column === "done" || row.status === "failed");
|
||||
|
||||
@@ -57,6 +57,19 @@ import { getRelativeTimeBucket } from "../utils/relativeTimeAgo";
|
||||
|
||||
type MailboxTab = "inbox" | "outbox" | "archived" | "agents";
|
||||
|
||||
/*
|
||||
FNXC:LifecycleColumnCensus 2026-08-13-21:58:
|
||||
DELIBERATE-LITERAL — mailbox folder tab, not a board column.
|
||||
|
||||
FN-9014 named a folder `archived`. The tab comparison is that folder switch. Converting it to
|
||||
resolveLifecycleColumns would ask a workflow which lane a mailbox folder is in. Keep the
|
||||
comparison inside this helper so a real board guard that happens to use the name `activeTab`
|
||||
still counts in the census.
|
||||
*/
|
||||
function isMailboxArchivedTab(tab: MailboxTab): boolean {
|
||||
return tab === "archived";
|
||||
}
|
||||
|
||||
const ALL_AGENTS_MAILBOX_ID = "__all_agents__";
|
||||
|
||||
interface MailboxModalProps {
|
||||
@@ -488,7 +501,7 @@ export function MailboxModal({
|
||||
if (!isOpen) return;
|
||||
if (activeTab === "inbox") loadInbox();
|
||||
else if (activeTab === "outbox") loadOutbox();
|
||||
else if (activeTab === "archived") loadArchivedInbox();
|
||||
else if (isMailboxArchivedTab(activeTab)) loadArchivedInbox();
|
||||
}, [isOpen, activeTab, loadInbox, loadOutbox, loadArchivedInbox]);
|
||||
|
||||
// Load agent mailbox when selected
|
||||
@@ -701,7 +714,7 @@ export function MailboxModal({
|
||||
handleCloseMessage();
|
||||
if (activeTab === "inbox") loadInbox();
|
||||
else if (activeTab === "outbox") loadOutbox();
|
||||
else if (activeTab === "archived") loadArchivedInbox();
|
||||
else if (isMailboxArchivedTab(activeTab)) loadArchivedInbox();
|
||||
else if (selectedAgentId === ALL_AGENTS_MAILBOX_ID) loadAllAgentsMailbox();
|
||||
else if (selectedAgentId) loadAgentMailbox(selectedAgentId);
|
||||
void refreshUnreadCount();
|
||||
@@ -905,7 +918,7 @@ export function MailboxModal({
|
||||
<Send size={14} />
|
||||
<span>{t("mailbox.outboxTab", "Outbox")}</span>
|
||||
</button>
|
||||
<button className={`btn btn-sm btn-secondary mailbox-tab ${activeTab === "archived" ? "active" : ""}`} onClick={() => { consumeCurrentDeepLink(); setActiveTab("archived"); setSelectedMessage(null); }} data-testid="mailbox-tab-archived"><Archive size={14} /><span>Archived</span></button>
|
||||
<button className={`btn btn-sm btn-secondary mailbox-tab ${isMailboxArchivedTab(activeTab) ? "active" : ""}`} onClick={() => { consumeCurrentDeepLink(); setActiveTab("archived"); setSelectedMessage(null); }} data-testid="mailbox-tab-archived"><Archive size={14} /><span>Archived</span></button>
|
||||
<button
|
||||
className={`btn btn-sm btn-secondary mailbox-tab ${activeTab === "agents" ? "active" : ""}`}
|
||||
onClick={() => { consumeCurrentDeepLink(); setActiveTab("agents"); setSelectedMessage(null); }}
|
||||
@@ -1092,7 +1105,7 @@ export function MailboxModal({
|
||||
{!selectedMessage && !showComposer && (
|
||||
<>
|
||||
{/* Inbox Tab */}
|
||||
{activeTab === "archived" && (
|
||||
{isMailboxArchivedTab(activeTab) && (
|
||||
<div className="mailbox-list" data-testid="mailbox-archived-list">
|
||||
{archivedInbox?.messages.length === 0 && <div className="mailbox-empty" data-testid="mailbox-archived-empty">No archived messages</div>}
|
||||
{archivedInbox?.messages.map((message) => <button type="button" className="mailbox-item" key={message.id} onClick={() => void handleOpenMessage(message)} data-testid={`mailbox-item-${message.id}`}>{message.content}</button>)}
|
||||
|
||||
@@ -62,6 +62,19 @@ import { getRelativeTimeBucket } from "../utils/relativeTimeAgo";
|
||||
|
||||
type MailboxTab = "inbox" | "outbox" | "archived" | "agents" | "approvals";
|
||||
|
||||
/*
|
||||
FNXC:LifecycleColumnCensus 2026-08-13-21:58:
|
||||
DELIBERATE-LITERAL — mailbox folder tab, not a board column.
|
||||
|
||||
FN-9014 named a folder `archived`. The tab comparison is that folder switch. Converting it to
|
||||
resolveLifecycleColumns would ask a workflow which lane a mailbox folder is in. Keep the
|
||||
comparison inside this helper so a real board guard that happens to use the name `activeTab`
|
||||
still counts in the census.
|
||||
*/
|
||||
function isMailboxArchivedTab(tab: MailboxTab): boolean {
|
||||
return tab === "archived";
|
||||
}
|
||||
|
||||
interface MailboxViewProps {
|
||||
projectId?: string;
|
||||
addToast?: (msg: string, type?: "success" | "error") => void;
|
||||
@@ -573,7 +586,7 @@ export function MailboxView({
|
||||
useEffect(() => {
|
||||
if (activeTab === "inbox") loadInbox();
|
||||
else if (activeTab === "outbox") loadOutbox();
|
||||
else if (activeTab === "archived") loadArchivedInbox();
|
||||
else if (isMailboxArchivedTab(activeTab)) loadArchivedInbox();
|
||||
else if (activeTab === "agents") loadAgents();
|
||||
else if (activeTab === "approvals") {
|
||||
void loadApprovals(approvalSubTab);
|
||||
@@ -783,7 +796,7 @@ export function MailboxView({
|
||||
try {
|
||||
await archiveMessage(id, projectId);
|
||||
dismissMessage();
|
||||
if (activeTab === "archived") loadArchivedInbox();
|
||||
if (isMailboxArchivedTab(activeTab)) loadArchivedInbox();
|
||||
else if (activeTab === "outbox") loadOutbox();
|
||||
else if (activeTab === "inbox") loadInbox();
|
||||
else if (selectedAgentId === ALL_AGENTS_MAILBOX_ID) loadAllAgentsMailbox();
|
||||
@@ -812,7 +825,7 @@ export function MailboxView({
|
||||
// Refresh current tab
|
||||
if (activeTab === "inbox") loadInbox();
|
||||
else if (activeTab === "outbox") loadOutbox();
|
||||
else if (activeTab === "archived") loadArchivedInbox();
|
||||
else if (isMailboxArchivedTab(activeTab)) loadArchivedInbox();
|
||||
else if (selectedAgentId === ALL_AGENTS_MAILBOX_ID) loadAllAgentsMailbox();
|
||||
else if (selectedAgentId) loadAgentMailbox(selectedAgentId);
|
||||
addToast?.("Message deleted", "success");
|
||||
@@ -1134,7 +1147,7 @@ export function MailboxView({
|
||||
|
||||
const renderListPane = () => (
|
||||
<>
|
||||
{activeTab === "archived" && (
|
||||
{isMailboxArchivedTab(activeTab) && (
|
||||
<div className="mailbox-list" data-testid="mailbox-archived-list">
|
||||
{isLoading && !archivedInbox && <MailboxSkeleton />}
|
||||
{archivedInbox?.messages.length === 0 && <div className="mailbox-empty" data-testid="mailbox-archived-empty">No archived messages</div>}
|
||||
@@ -1576,7 +1589,7 @@ export function MailboxView({
|
||||
onClick={() => {
|
||||
if (activeTab === "inbox") loadInbox();
|
||||
else if (activeTab === "outbox") loadOutbox();
|
||||
else if (activeTab === "archived") loadArchivedInbox();
|
||||
else if (isMailboxArchivedTab(activeTab)) loadArchivedInbox();
|
||||
else if (activeTab === "approvals") loadApprovals(approvalSubTab);
|
||||
else if (selectedAgentId === ALL_AGENTS_MAILBOX_ID) loadAllAgentsMailbox();
|
||||
else if (selectedAgentId) loadAgentMailbox(selectedAgentId);
|
||||
@@ -1610,7 +1623,7 @@ export function MailboxView({
|
||||
<Send size={14} />
|
||||
<span>{t("mailbox.outbox", "Outbox")}</span>
|
||||
</button>
|
||||
<button className={`btn btn-sm btn-secondary mailbox-tab ${activeTab === "archived" ? "active" : ""}`} onClick={() => handleSelectTab("archived")} data-testid="mailbox-tab-archived">Archived</button>
|
||||
<button className={`btn btn-sm btn-secondary mailbox-tab ${isMailboxArchivedTab(activeTab) ? "active" : ""}`} onClick={() => handleSelectTab("archived")} data-testid="mailbox-tab-archived">Archived</button>
|
||||
<button
|
||||
className={`btn btn-sm btn-secondary mailbox-tab ${activeTab === "agents" ? "active" : ""}`}
|
||||
onClick={() => handleSelectTab("agents")}
|
||||
|
||||
@@ -14,13 +14,16 @@ the engine copy is asserted by reading the source file directly.
|
||||
*/
|
||||
const CARVEOUT_MARKER = "Exception — pending approval.";
|
||||
|
||||
// FNXC:ExecutorPrompt 2026-08-15-19:10: package code organization waves moved
|
||||
// EXECUTOR_SYSTEM_PROMPT out of the thin executor.ts shell into
|
||||
// executor/system-prompt.ts, and core's agent-prompts.ts into agents/.
|
||||
function readExecutorSourcePrompt(): string {
|
||||
const executorTsPath = fileURLToPath(new URL("../executor.ts", import.meta.url));
|
||||
const executorTsPath = fileURLToPath(new URL("../executor/system-prompt.ts", import.meta.url));
|
||||
return readFileSync(executorTsPath, "utf8");
|
||||
}
|
||||
|
||||
function readAgentPromptsSource(): string {
|
||||
const agentPromptsTsPath = fileURLToPath(new URL("../../../core/src/agent-prompts.ts", import.meta.url));
|
||||
const agentPromptsTsPath = fileURLToPath(new URL("../../../core/src/agents/agent-prompts.ts", import.meta.url));
|
||||
return readFileSync(agentPromptsTsPath, "utf8");
|
||||
}
|
||||
|
||||
|
||||
@@ -453,19 +453,33 @@ describe("one lane snapshot per recovery, across every classifier", () => {
|
||||
(PR #2703 review) — its eligibility check and its review branch each resolved independently. */
|
||||
const SELF_CONTAINED = ["handleNonContinuableSessionError"];
|
||||
|
||||
/*
|
||||
FNXC:CodeOrganization 2026-08-15-19:10 (executor peels):
|
||||
Every classifier moved out of the TaskExecutor class body into its own executor/ free-function
|
||||
module (`export async function <name>(`). The scan follows the peel: find the one module that
|
||||
declares the function and bound its body by the next top-level `export` declaration — the same
|
||||
wrong-window hazard the `private ` bound guarded against, now at module granularity.
|
||||
*/
|
||||
const CLASSIFIER_MODULES: Record<string, string> = {
|
||||
isRetryableBenignMergePauseAbort: "is-retryable-benign-merge-pause-abort.ts",
|
||||
isBenignManualMergeHoldPauseAbort: "is-benign-manual-merge-hold-pause-abort.ts",
|
||||
handleStaleInReviewPlanPauseAbortReplay: "handle-stale-in-review-plan-pause-abort-replay.ts",
|
||||
handleStaleInReviewParsePauseAbortReplay: "handle-stale-in-review-parse-pause-abort-replay.ts",
|
||||
isReentrantPausedAbortedInFlightNode: "is-reentrant-paused-aborted-in-flight-node.ts",
|
||||
routeUnusableWorktreeGraphFailureToRecovery: "route-unusable-worktree-graph-failure-to-recovery.ts",
|
||||
routeGraphFailureToExecutionResume: "route-graph-failure-to-execution-resume.ts",
|
||||
handleNonContinuableSessionError: "non-continuable-session.ts",
|
||||
};
|
||||
|
||||
async function methodBody(name: string): Promise<string> {
|
||||
const { readFile } = await import("node:fs/promises");
|
||||
const source = await readFile(new URL("../executor.ts", import.meta.url), "utf8");
|
||||
const module = CLASSIFIER_MODULES[name];
|
||||
expect(module, `${name} has no mapped executor/ module — update this test, do not delete it`).toBeDefined();
|
||||
const source = await readFile(new URL(`../executor/${module}`, import.meta.url), "utf8");
|
||||
const code = source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
|
||||
const start = code.indexOf(`private async ${name}(`);
|
||||
const start = code.indexOf(`export async function ${name}(`);
|
||||
expect(start, `${name} not found — update this test, do not delete it`).toBeGreaterThan(-1);
|
||||
/*
|
||||
Bounded by the next `private ` declaration of ANY kind. My first version bounded on the next member of
|
||||
the same list, so the last entry's window ran to EOF and it accused a method of a call living 1200 lines
|
||||
away. A ratchet with the wrong window accuses the wrong function — worse than no ratchet, because the
|
||||
"fix" lands on code that was already correct.
|
||||
*/
|
||||
const next = code.indexOf("\n private ", start + 1);
|
||||
const next = code.indexOf("\nexport ", start + 1);
|
||||
return code.slice(start, next === -1 ? code.length : next);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import "./executor-test-helpers.js";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { TaskExecutor, evaluateTaskDoneRefusal } from "../executor.js";
|
||||
import { TaskExecutor, evaluateTaskDoneRefusal, evaluateImplicitCompletionRefusal } from "../executor.js";
|
||||
/*
|
||||
FNXC:CodeOrganization 2026-08-15-19:10:
|
||||
evaluateImplicitCompletionRefusal was peeled off the TaskExecutor class into the pure
|
||||
executor/completion-predicates.ts module (re-exported from executor.js). Same behavior,
|
||||
same (task, codeReviewVerdicts) signature — these tests call the free function directly.
|
||||
*/
|
||||
import { resetExecutorMocks } from "./executor-test-helpers.js";
|
||||
import { evaluateSkipBypassTaint } from "@fusion/core";
|
||||
|
||||
@@ -48,26 +54,23 @@ describe("TaskExecutor skip-bypass taint (FN-8141)", () => {
|
||||
});
|
||||
|
||||
it("implicit completion is REFUSED when steps were skipped after a bulk-step-completion refusal", () => {
|
||||
const executor = new TaskExecutor(createStore(), "/tmp/test");
|
||||
// The FN-8141 sequence: 3 done + 2 skipped, refusal marker active, no accepted done.
|
||||
const task = taskWith(["done", "done", "done", "skipped", "skipped"], "2026-07-16T21:40:00.000Z");
|
||||
const result = (executor as any).evaluateImplicitCompletionRefusal(task, new Map());
|
||||
const result = evaluateImplicitCompletionRefusal(task, new Map()) as any;
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.refusalClass).toBe("bulk-step-completion-without-review");
|
||||
expect(result.reason).toContain("skipped after a bulk-step-completion refusal");
|
||||
});
|
||||
|
||||
it("implicit completion is ALLOWED for a clean all-done/skipped task with no refusal marker", () => {
|
||||
const executor = new TaskExecutor(createStore(), "/tmp/test");
|
||||
const task = taskWith(["done", "skipped"], undefined);
|
||||
const result = (executor as any).evaluateImplicitCompletionRefusal(task, new Map());
|
||||
const result = evaluateImplicitCompletionRefusal(task, new Map());
|
||||
expect(result).toEqual({ ok: true });
|
||||
});
|
||||
|
||||
it("implicit completion is ALLOWED for a tainted task once every step is genuinely done (no skips left)", () => {
|
||||
const executor = new TaskExecutor(createStore(), "/tmp/test");
|
||||
const task = taskWith(["done", "done", "done"], "2026-07-16T21:40:00.000Z");
|
||||
const result = (executor as any).evaluateImplicitCompletionRefusal(task, new Map());
|
||||
const result = evaluateImplicitCompletionRefusal(task, new Map());
|
||||
expect(result).toEqual({ ok: true });
|
||||
});
|
||||
|
||||
|
||||
@@ -12,34 +12,34 @@ export type SeverityManifestEntry = {
|
||||
};
|
||||
|
||||
export const logSeverityManifest: SeverityManifestEntry[] = [
|
||||
{ pkg: "engine", file: "plugin-runner.ts", anchor: "condition evaluated false", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Executor runtime environment event: ${event}", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Tool execution event: ${event}", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Runtime instantiation event: ${event}", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugin-runner.ts", anchor: "debug: (...args: unknown[]) => this.log.debug(prefix, ...args)", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Tools cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Routes cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugin-runner.ts", anchor: "UI slots cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugin-runner.ts", anchor: "UI contributions cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Runtimes cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugin-runner.ts", anchor: "CLI provider contributions cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Skills cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugin-runner.ts", anchor: "MCP servers cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Workflow steps cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Workflow extensions cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Workflow step templates cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Plugin traits cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Prompt contributions cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugin-runner.ts", anchor: "Setup cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "condition evaluated false", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Executor runtime environment event: ${event}", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Tool execution event: ${event}", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Runtime instantiation event: ${event}", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "debug: (...args: unknown[]) => this.log.debug(prefix, ...args)", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Tools cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Routes cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "UI slots cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "UI contributions cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Runtimes cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "CLI provider contributions cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Skills cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "MCP servers cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Workflow steps cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Workflow extensions cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Workflow step templates cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Plugin traits cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Prompt contributions cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "plugins/plugin-runner.ts", anchor: "Setup cache invalidated", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "self-healing.ts", anchor: "Cleaned ${cleaned} stale AI merge temp worktree(s)", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "self-healing.ts", anchor: "cleanup-old-chats\" removed stale data", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "self-healing.ts", anchor: "cleanup-old-mail\" removed stale data", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "self-healing.ts", anchor: "Auto-archiving ${stale.length}", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "self-healing.ts", anchor: "auto-archive: archived", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "self-healing.ts", anchor: "Auto-archived ${archived} stale done task(s)", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "pty-native.ts", anchor: "Pre-loaded native module via dlopen", priorSeverity: "console", severity: "debug" },
|
||||
{ pkg: "engine", file: "pty-native.ts", anchor: "dlopen pre-load failed (continuing)", priorSeverity: "console", severity: "debug" },
|
||||
{ pkg: "engine", file: "goal-anchoring-audit.ts", anchor: "goal retrieval audit emission skipped", priorSeverity: "console", severity: "debug" },
|
||||
{ pkg: "engine", file: "cli-runtime/pty-native.ts", anchor: "Pre-loaded native module via dlopen", priorSeverity: "console", severity: "debug" },
|
||||
{ pkg: "engine", file: "cli-runtime/pty-native.ts", anchor: "dlopen pre-load failed (continuing)", priorSeverity: "console", severity: "debug" },
|
||||
{ pkg: "engine", file: "goals/goal-anchoring-audit.ts", anchor: "goal retrieval audit emission skipped", priorSeverity: "console", severity: "debug" },
|
||||
{ pkg: "engine", file: "runtimes/child-process-worker.ts", anchor: "Child process worker starting", priorSeverity: "log", severity: "debug" },
|
||||
/*
|
||||
FNXC:EngineDiagnostics 2026-07-30-04:00:
|
||||
@@ -93,8 +93,8 @@ export const logSeverityManifest: SeverityManifestEntry[] = [
|
||||
triple sites, and zero-recovery gates are pinned in the spam-contract suite instead.
|
||||
*/
|
||||
{ pkg: "engine", file: "triage.ts", anchor: "planLog.debug(`${task.id}: planning in ${leanPlanning ? \"fast\" : \"standard\"} mode`)", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "session-token-usage.ts", anchor: "cacheMetricsLog.debug(JSON.stringify({", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "executor.ts", anchor: "tokenCacheMetricsLog.debug(JSON.stringify({", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "execution/session-token-usage.ts", anchor: "cacheMetricsLog.debug(JSON.stringify({", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "executor/persist-token-usage.ts", anchor: "tokenCacheMetricsLog.debug(JSON.stringify({", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "runtimes/in-process-runtime.ts", anchor: "runtimeLog.debug(`Specifying ${t.id}...`)", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "triage.ts", anchor: "planLog.debug(`${taskId}: failed to read PROMPT.md during ${context} (${promptPath}): ${msg}`)", priorSeverity: "warn", severity: "debug" },
|
||||
/*
|
||||
@@ -106,19 +106,19 @@ export const logSeverityManifest: SeverityManifestEntry[] = [
|
||||
{ pkg: "engine", file: "scheduler.ts", anchor: "No linked feature found for task ${taskId}", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "scheduler.ts", anchor: "Task created — triggering scheduling", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "scheduler.ts", anchor: "Task moved to ${to} — triggering scheduling", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "auto-claim-snapshot.ts", anchor: "invalidate reason=${reason}", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "scheduling/auto-claim-snapshot.ts", anchor: "invalidate reason=${reason}", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "agent-heartbeat.ts", anchor: "Assignment trigger skipped for ${agent.id} (ephemeral/internal)", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "agent-heartbeat.ts", anchor: "Assignment trigger skipped for ${agent.id} (disabled)", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "agent-heartbeat.ts", anchor: "Assignment trigger skipped for ${agent.id} (active run)", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "runtimes/in-process-runtime.ts", anchor: "Scheduled task ${task.id}", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "runtimes/in-process-runtime.ts", anchor: "Started executing task ${task.id} in ${worktreePath}", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "executor.ts", anchor: "executorLog.debug(`${task.id}: executor runtime env injected", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "executor.ts", anchor: "executorLog.debug(`${live.id}: graph node '${node.id}' runtime env injected", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "executor.ts", anchor: "executorLog.debug(`${task.id}: workflow node '${nodeId}' acquired worktree", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "executor.ts", anchor: "executorLog.debug(`${task.id}: captured baseCommitSha ${baseCommitSha.slice(0, 7)}`)", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "executor.ts", anchor: "executorLog.debug(`${taskId}: reconcile step source governed by parse-steps", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "run-verification-tool.ts", anchor: "executorLog.debug(`[fn_run_verification] command failed (exit=", priorSeverity: "warn", severity: "debug" },
|
||||
{ pkg: "engine", file: "worktree-acquisition.ts", anchor: "logger.debug(`Reusing existing worktree: ${path}`)", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "worktree-acquisition.ts", anchor: "logger.debug(`Reusing existing worktree: ${worktreePath}`)", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "executor/run-implementation.ts", anchor: "executorLog.debug(`${task.id}: executor runtime env injected", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "executor/run-graph-custom-node.ts", anchor: "executorLog.debug(`${live.id}: graph node '${node.id}' runtime env injected", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "executor/ensure-graph-custom-node-worktree.ts", anchor: "executorLog.debug(`${task.id}: workflow node '${nodeId}' acquired worktree", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "executor/worktree-git-refs.ts", anchor: "executorLog.debug(`${task.id}: captured baseCommitSha ${baseCommitSha.slice(0, 7)}`)", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "executor/reconcile-steps-from-git-history.ts", anchor: "executorLog.debug(`${taskId}: reconcile step source governed by parse-steps", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "execution/run-verification-tool.ts", anchor: "executorLog.debug(`[fn_run_verification] command failed (exit=", priorSeverity: "warn", severity: "debug" },
|
||||
{ pkg: "engine", file: "worktree/worktree-acquisition.ts", anchor: "logger.debug(`Reusing existing worktree: ${path}`)", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "engine", file: "worktree/worktree-acquisition.ts", anchor: "logger.debug(`Reusing existing worktree: ${worktreePath}`)", priorSeverity: "log", severity: "debug" },
|
||||
{ pkg: "core", file: "postgres/embedded-lifecycle.ts", anchor: "log.debug(`embedded postgres: already running on port", priorSeverity: "log", severity: "debug" },
|
||||
];
|
||||
|
||||
@@ -63,7 +63,7 @@ describe("log severity spam contract (source)", () => {
|
||||
|
||||
it("all type=info skill diagnostics (Requested skill listings and not-found) route to debug", () => {
|
||||
const pi = readSrc("pi.ts");
|
||||
const resolver = readSrc("skill-resolver.ts");
|
||||
const resolver = readSrc("cli-runtime/skill-resolver.ts");
|
||||
expect(pi).toMatch(/else if \(diag\.type === "info"\) piLog\.debug\(msg\)/);
|
||||
expect(resolver).toMatch(/isSkillInfoDiagnostic/);
|
||||
expect(resolver).toMatch(/else if \(isSkillInfoDiagnostic\(diag\)\) piLog\.debug\(msg\)/);
|
||||
@@ -71,7 +71,7 @@ describe("log severity spam contract (source)", () => {
|
||||
});
|
||||
|
||||
it("activity-recorded heartbeats and periodic stuck poll use debug", () => {
|
||||
const src = readSrc("stuck-task-detector.ts");
|
||||
const src = readSrc("healing/stuck-task-detector.ts");
|
||||
expect(src).toMatch(/stuckLog\.debug\(\s*`Activity recorded for/);
|
||||
expect(src).toMatch(/stuckLog\.debug\("Running periodic stuck task check \(polling\)"\)/);
|
||||
expect(src).not.toMatch(/stuckLog\.log\(\s*`Activity recorded for/);
|
||||
@@ -86,26 +86,26 @@ describe("log severity spam contract (source)", () => {
|
||||
});
|
||||
|
||||
it("cron multi-scope skip chatter uses debug; execute stays on log", () => {
|
||||
const src = readSrc("cron-runner.ts");
|
||||
const src = readSrc("scheduling/cron-runner.ts");
|
||||
expect(src).toMatch(/log\.debug\(`Skipping \$\{schedule\.name\}[\s\S]*already executed from another scope/);
|
||||
expect(src).toMatch(/log\.debug\(`Skipping \$\{schedule\.name\}[\s\S]*claim lost to another poller/);
|
||||
expect(src).toMatch(/log\.log\(`Executing \$\{schedule\.name\}/);
|
||||
});
|
||||
|
||||
it("plugin skill contribution/merge chatter uses debug", () => {
|
||||
const src = readSrc("session-skill-context.ts");
|
||||
const src = readSrc("cli-runtime/session-skill-context.ts");
|
||||
expect(src).toMatch(/piLog\.debug\(`\[skills\] Plugin \$\{pluginId\} contributes skill:/);
|
||||
expect(src).toMatch(/piLog\.debug\(\s*`\[skills\] Merged \$\{appendedPluginNames\.length\}/);
|
||||
});
|
||||
|
||||
it("hold-release capacity race uses debug like deferred-no-slot", () => {
|
||||
const src = readSrc("hold-release.ts");
|
||||
const src = readSrc("execution/hold-release.ts");
|
||||
expect(src).toMatch(/schedulerLog\.debug\(`Hold release for \$\{task\.id\} rejected on capacity/);
|
||||
expect(src).toMatch(/schedulerLog\.debug\(`Hold release for \$\{task\.id\} deferred — no reservable slot/);
|
||||
});
|
||||
|
||||
it("routine-scheduler re-entrance and pause no-ops use debug", () => {
|
||||
const src = readSrc("routine-scheduler.ts");
|
||||
const src = readSrc("scheduling/routine-scheduler.ts");
|
||||
expect(src).toMatch(/logger\.debug\("Tick already in progress, skipping"\)/);
|
||||
expect(src).toMatch(/logger\.debug\(\s*`Paused: globalPause=/);
|
||||
expect(src).not.toMatch(/logger\.log\("Tick already in progress, skipping"\)/);
|
||||
@@ -113,7 +113,7 @@ describe("log severity spam contract (source)", () => {
|
||||
});
|
||||
|
||||
it("peer-exchange zero-work sync cycle uses debug; non-zero/error stay on log", () => {
|
||||
const src = readSrc("peer-exchange-service.ts");
|
||||
const src = readSrc("project/peer-exchange-service.ts");
|
||||
expect(src).toMatch(/peerExchangeLog\.debug\(`Starting sync with \$\{onlineRemoteNodes\.length\} peers`\)/);
|
||||
expect(src).toMatch(/peerExchangeLog\.debug\(\s*`Sync complete: \$\{onlineRemoteNodes\.length\} peers synced\./);
|
||||
// Non-zero discovery path and error summary remain log
|
||||
@@ -135,15 +135,15 @@ describe("log severity spam contract (source)", () => {
|
||||
exclusions, zero-recovery no-ops, and metrics JSON must stay off default info/warn.
|
||||
*/
|
||||
it("session setup, track bookkeeping, skill exclusion, and zero-recovery stay debug-gated", () => {
|
||||
const session = readSrc("agent-session-helpers.ts");
|
||||
const stuck = readSrc("stuck-task-detector.ts");
|
||||
const session = readSrc("agents/agent-session-helpers.ts");
|
||||
const stuck = readSrc("healing/stuck-task-detector.ts");
|
||||
const triage = readSrc("triage.ts");
|
||||
const resolver = readSrc("skill-resolver.ts");
|
||||
const resolver = readSrc("cli-runtime/skill-resolver.ts");
|
||||
const selfHealing = readSrc("self-healing.ts");
|
||||
const mission = readSrc("mission-execution-loop.ts");
|
||||
const mission = readSrc("missions/mission-execution-loop.ts");
|
||||
const runtime = readSrc("runtimes/in-process-runtime.ts");
|
||||
const tokenUsage = readSrc("session-token-usage.ts");
|
||||
const executor = readSrc("executor.ts");
|
||||
const tokenUsage = readSrc("execution/session-token-usage.ts");
|
||||
const executor = readSrc("executor/persist-token-usage.ts");
|
||||
|
||||
expect(session).toMatch(/sessionLog\.debug\(\s*`\[\$\{sessionPurpose\}\] grok-cli fallback/);
|
||||
expect(session).toMatch(/sessionLog\.debug\(\s*`\[\$\{sessionPurpose\}\] Using runtime/);
|
||||
@@ -178,7 +178,7 @@ describe("log severity spam contract (source)", () => {
|
||||
|
||||
it("self-healing no-action/skip, worktree-pool probes, and ntfy bookkeeping use debug", () => {
|
||||
const sh = readSrc("self-healing.ts");
|
||||
const wt = readSrc("worktree-pool.ts");
|
||||
const wt = readSrc("worktree/worktree-pool.ts");
|
||||
const ntfy = readSrc("notification/ntfy-provider.ts");
|
||||
const notify = readSrc("notification/notification-service.ts");
|
||||
expect(sh).toMatch(/log\.debug\(`\[\$\{stage\}\] \$\{task\.id\}: triple-proof not satisfied — no action/);
|
||||
@@ -192,7 +192,7 @@ describe("log severity spam contract (source)", () => {
|
||||
});
|
||||
|
||||
it("pi session-purpose runtime routing and skip bookkeeping uses debug", () => {
|
||||
const runtime = readSrc("runtime-resolution.ts");
|
||||
const runtime = readSrc("execution/runtime-resolution.ts");
|
||||
const pi = readSrc("pi.ts");
|
||||
expect(runtime).toMatch(/runtimeLog\.debug\(`\[\$\{sessionPurpose\}\] No runtime hint configured/);
|
||||
expect(runtime).toMatch(/runtimeLog\.debug\(`\[\$\{sessionPurpose\}\] Runtime hint is "pi\/default"/);
|
||||
@@ -203,8 +203,8 @@ describe("log severity spam contract (source)", () => {
|
||||
});
|
||||
|
||||
it("checkpoint bookkeeping (self-improve + RETHINK rewind) uses debug", () => {
|
||||
const improve = readSrc("agent-self-improve.ts");
|
||||
const step = readSrc("step-runner.ts");
|
||||
const improve = readSrc("agents/agent-self-improve.ts");
|
||||
const step = readSrc("execution/step-runner.ts");
|
||||
expect(improve).toMatch(/selfImproveLog\.debug\(`Recorded self-improve checkpoint for/);
|
||||
expect(step).toMatch(/executorLog\.debug\(`\$\{taskId\}: RETHINK — session rewound to checkpoint/);
|
||||
expect(step).toMatch(/executorLog\.debug\(`\$\{taskId\}: RETHINK — no session checkpoint for step/);
|
||||
@@ -213,7 +213,7 @@ describe("log severity spam contract (source)", () => {
|
||||
|
||||
it("merger intermediate plumbing uses debug; outcomes stay at log", () => {
|
||||
const merger = readSrc("merger.ts");
|
||||
const conflict = readSrc("merger-conflict-resolution.ts");
|
||||
const conflict = readSrc("merge/merger-conflict-resolution.ts");
|
||||
expect(conflict).toMatch(/mergerLog\.debug\(`Auto-resolved \$\{filePath\} using --ours`\)/);
|
||||
expect(merger).toMatch(/mergerLog\.debug\(`\$\{taskId\}: merge details stored/);
|
||||
expect(merger).toMatch(/mergerLog\.debug\(`\$\{taskId\}: git pull --rebase succeeded/);
|
||||
@@ -225,8 +225,8 @@ describe("log severity spam contract (source)", () => {
|
||||
});
|
||||
|
||||
it("foreach step skip/rework and per-step success use debug", () => {
|
||||
const foreach = readSrc("workflow-graph-foreach.ts");
|
||||
const exec = readSrc("executor.ts");
|
||||
const foreach = readSrc("workflows/workflow-graph-foreach.ts");
|
||||
const exec = readSrc("executor/run-implementation.ts");
|
||||
expect(foreach).toMatch(/schedulerLog\.debug\(\s*`foreach \$\{foreachNode\.id\} for task \$\{env\.task\.id\}: skipping step/);
|
||||
expect(foreach).toMatch(/schedulerLog\.debug\(\s*`foreach \$\{foreachNode\.id\} step \$\{inst\.stepIndex\}: integration-conflict/);
|
||||
expect(foreach).not.toMatch(/schedulerLog\.log\(\s*`foreach \$\{foreachNode\.id\} for task/);
|
||||
@@ -235,21 +235,25 @@ describe("log severity spam contract (source)", () => {
|
||||
});
|
||||
|
||||
it("executor high-frequency dispatch/session bookkeeping uses debug", () => {
|
||||
const src = readSrc("executor.ts");
|
||||
expect(src).toMatch(/executorLog\.debug\(`TaskExecutor constructed/);
|
||||
expect(src).toMatch(/executorLog\.debug\(`execute\(\) called for \$\{task\.id\} \(claimed=/);
|
||||
expect(src).toMatch(/executorLog\.debug\(`\$\{task\.id\}: worktree ready at/);
|
||||
expect(src).toMatch(/executorLog\.debug\(`\$\{task\.id\}: session registered/);
|
||||
expect(src).toMatch(/executorLog\.debug\(`\$\{task\.id\}: calling promptWithFallback/);
|
||||
expect(src).toMatch(/executorLog\.debug\(`\[workflow-graph\] \$\{event\.type\}/);
|
||||
expect(src).toMatch(/executorLog\.debug\(`\[workflow-column-boundary\]/);
|
||||
// FNXC:EngineDiagnostics 2026-08-15-19:10: executor.ts peels (package code organization waves) moved these call sites into executor/ modules; anchors verified moved, not duplicated.
|
||||
const impl = readSrc("executor/run-implementation.ts");
|
||||
const lifecycle = readSrc("executor/wire-executor-lifecycle.ts");
|
||||
const graph = readSrc("executor/execute-workflow-graph.ts");
|
||||
const boundary = readSrc("executor/build-column-boundary-hooks.ts");
|
||||
expect(lifecycle).toMatch(/executorLog\.debug\(`TaskExecutor constructed/);
|
||||
expect(impl).toMatch(/executorLog\.debug\(`execute\(\) called for \$\{task\.id\} \(claimed=/);
|
||||
expect(impl).toMatch(/executorLog\.debug\(`\$\{task\.id\}: worktree ready at/);
|
||||
expect(impl).toMatch(/executorLog\.debug\(`\$\{task\.id\}: session registered/);
|
||||
expect(impl).toMatch(/executorLog\.debug\(`\$\{task\.id\}: calling promptWithFallback/);
|
||||
expect(graph).toMatch(/executorLog\.debug\(`\[workflow-graph\] \$\{event\.type\}/);
|
||||
expect(boundary).toMatch(/executorLog\.debug\(`\[workflow-column-boundary\]/);
|
||||
// Lifecycle outcomes stay at log
|
||||
expect(src).toMatch(/executorLog\.log\(`Starting \$\{task\.id\}:/);
|
||||
expect(src).not.toMatch(/executorLog\.log\(`execute\(\) called for \$\{task\.id\} \(claimed=/);
|
||||
expect(impl).toMatch(/executorLog\.log\(`Starting \$\{task\.id\}:/);
|
||||
expect(impl).not.toMatch(/executorLog\.log\(`execute\(\) called for \$\{task\.id\} \(claimed=/);
|
||||
});
|
||||
|
||||
it("MCP server connected success chatter uses logger.debug", () => {
|
||||
const src = readSrc("mcp-session-tools.ts");
|
||||
const src = readSrc("mcp/mcp-session-tools.ts");
|
||||
expect(src).toMatch(/opts\.logger\.debug\(connectMsg\)/);
|
||||
expect(src).toMatch(/MCP server connected for pi session/);
|
||||
// Must not route the success line only through log without preferring debug.
|
||||
@@ -257,10 +261,10 @@ describe("log severity spam contract (source)", () => {
|
||||
});
|
||||
|
||||
it("fn_run_verification and green verification result paths use debug", () => {
|
||||
const tool = readSrc("run-verification-tool.ts");
|
||||
const utils = readSrc("verification-utils.ts");
|
||||
const stuck = readSrc("stuck-task-detector.ts");
|
||||
const exec = readSrc("executor.ts");
|
||||
const tool = readSrc("execution/run-verification-tool.ts");
|
||||
const utils = readSrc("execution/verification-utils.ts");
|
||||
const stuck = readSrc("healing/stuck-task-detector.ts");
|
||||
const exec = readSrc("executor/deterministic-verification.ts");
|
||||
expect(tool).toMatch(/executorLog\.debug\(\s*`\[fn_run_verification\] command quiet for/);
|
||||
expect(tool).toMatch(/\(log\.debug \?\? log\.info\)\(/);
|
||||
expect(tool).toMatch(/if \(result\.success\) \{\s*\(log\.debug \?\? log\.info\)\(/);
|
||||
@@ -285,10 +289,14 @@ describe("log severity spam contract (source)", () => {
|
||||
it("busy-board lifecycle noise stays debug-gated; starts and creates stay log", () => {
|
||||
const scheduler = readSrc("scheduler.ts");
|
||||
const runtime = readSrc("runtimes/in-process-runtime.ts");
|
||||
const exec = readSrc("executor.ts");
|
||||
// FNXC:EngineDiagnostics 2026-08-15-19:10: executor.ts peels split these anchors across executor/ modules.
|
||||
const execImpl = readSrc("executor/run-implementation.ts");
|
||||
const execWorktreeCreate = readSrc("executor/worktree-create-conflict.ts");
|
||||
const execGitRefs = readSrc("executor/worktree-git-refs.ts");
|
||||
const execNodeWorktree = readSrc("executor/ensure-graph-custom-node-worktree.ts");
|
||||
const heartbeat = readSrc("agent-heartbeat.ts");
|
||||
const autoClaim = readSrc("auto-claim-snapshot.ts");
|
||||
const worktree = readSrc("worktree-acquisition.ts");
|
||||
const autoClaim = readSrc("scheduling/auto-claim-snapshot.ts");
|
||||
const worktree = readSrc("worktree/worktree-acquisition.ts");
|
||||
|
||||
expect(scheduler).toMatch(/schedulerLog\.debug\(`No linked feature found for task/);
|
||||
expect(scheduler).toMatch(/schedulerLog\.debug\("Task created — triggering scheduling"\)/);
|
||||
@@ -300,12 +308,12 @@ describe("log severity spam contract (source)", () => {
|
||||
expect(runtime).toMatch(/runtimeLog\.debug\(`Started executing task \$\{task\.id\} in \$\{worktreePath\}`\)/);
|
||||
expect(runtime).not.toMatch(/runtimeLog\.log\(`Scheduled task \$\{task\.id\}`\)/);
|
||||
|
||||
expect(exec).toMatch(/executorLog\.log\(`Starting \$\{task\.id\}:/);
|
||||
expect(exec).toMatch(/executorLog\.log\(`Worktree created:/);
|
||||
expect(exec).toMatch(/executorLog\.debug\(`\$\{task\.id\}: executor runtime env injected/);
|
||||
expect(exec).toMatch(/executorLog\.debug\(`\$\{task\.id\}: captured baseCommitSha/);
|
||||
expect(exec).toMatch(/executorLog\.debug\(`\$\{task\.id\}: workflow node '\$\{nodeId\}' acquired worktree/);
|
||||
expect(exec).not.toMatch(/executorLog\.log\(`\$\{task\.id\}: executor runtime env injected/);
|
||||
expect(execImpl).toMatch(/executorLog\.log\(`Starting \$\{task\.id\}:/);
|
||||
expect(execWorktreeCreate).toMatch(/executorLog\.log\(`Worktree created:/);
|
||||
expect(execImpl).toMatch(/executorLog\.debug\(`\$\{task\.id\}: executor runtime env injected/);
|
||||
expect(execGitRefs).toMatch(/executorLog\.debug\(`\$\{task\.id\}: captured baseCommitSha/);
|
||||
expect(execNodeWorktree).toMatch(/executorLog\.debug\(`\$\{task\.id\}: workflow node '\$\{nodeId\}' acquired worktree/);
|
||||
expect(execImpl).not.toMatch(/executorLog\.log\(`\$\{task\.id\}: executor runtime env injected/);
|
||||
|
||||
expect(heartbeat).toMatch(/heartbeatLog\.debug\(`Assignment trigger skipped for \$\{agent\.id\} \(ephemeral\/internal\)`\)/);
|
||||
expect(heartbeat).not.toMatch(/heartbeatLog\.log\(`Assignment trigger skipped for \$\{agent\.id\} \(ephemeral\/internal\)`\)/);
|
||||
|
||||
@@ -90,7 +90,19 @@ function sourceRoots(base: string = REPO_ROOT): string[] {
|
||||
* means a second owner now writes the claim, which is the FN-8504 shape. If that
|
||||
* is genuinely intended, the justification belongs beside the new entry.
|
||||
*/
|
||||
const PLANNING_CLAIM_WRITERS = ["packages/engine/src/triage.ts"];
|
||||
/*
|
||||
FNXC:PlanningClaimSingleWriter 2026-08-15-19:10 (FN-8998, d9a0ed7837):
|
||||
`self-healing.ts` is a SECOND intended writer, added by FN-8998's planning-lock
|
||||
reentry fix: `recordPlanningHandoffTransportFailure` RE-asserts `status: "planning"`
|
||||
(plus retry bookkeeping) on a card whose canonical planning handoff hit a transport
|
||||
failure, so the retry sweep can pick it up instead of stalling the lock. It is not a
|
||||
fresh claim by a second planner: the write goes through `updateTaskAtomic` with an
|
||||
`isTaskStillInPlanningStage(live)` re-check, the same guarded CAS shape the
|
||||
"planning-stage-guarded helper" assertion below demands of triage, so it cannot
|
||||
stamp `planning` onto a card the scheduler already advanced (the FN-7977/FN-8361 bug
|
||||
this ratchet exists to prevent).
|
||||
*/
|
||||
const PLANNING_CLAIM_WRITERS = ["packages/engine/src/self-healing.ts", "packages/engine/src/triage.ts"];
|
||||
|
||||
/**
|
||||
* Modules permitted to BIND the planning literal to a constant.
|
||||
|
||||
@@ -14,8 +14,8 @@ const REPO_ROOT = resolve(__dirname, "../../../..");
|
||||
const ENGINE_ROOT = join(REPO_ROOT, "packages/engine/src");
|
||||
|
||||
const DROP_BRIDGES = {
|
||||
"packages/engine/src/hybrid-executor.ts": "HybridExecutor",
|
||||
"packages/engine/src/project-manager.ts": "ProjectManager",
|
||||
"packages/engine/src/concurrency/hybrid-executor.ts": "HybridExecutor",
|
||||
"packages/engine/src/project/project-manager.ts": "ProjectManager",
|
||||
"packages/engine/src/runtimes/child-process-runtime.ts": "ChildProcessRuntime",
|
||||
"packages/engine/src/runtimes/in-process-runtime.ts": "InProcessRuntime",
|
||||
"packages/engine/src/runtimes/remote-node-runtime.ts": "RemoteNodeRuntime",
|
||||
@@ -67,6 +67,9 @@ describe("engine task:updated emit surface", () => {
|
||||
taskStore: inProcessUpstream,
|
||||
recordActivity: () => undefined,
|
||||
config: { projectId: "project" },
|
||||
// FNXC:WorkflowEvents 2026-08-15-19:10: the real task:updated registration now runs FN-7608-style approval-hold bookkeeping before re-emitting; the fixture supplies the sets so the actual forwarding body executes unchanged.
|
||||
approvalHeldTaskIds: new Set<string>(),
|
||||
approvalReleasedTaskIds: new Set<string>(),
|
||||
});
|
||||
(InProcessRuntime.prototype as any).setupEventForwarding.call(inProcess);
|
||||
|
||||
|
||||
@@ -46,7 +46,12 @@ FNXC:CodeOrganization 2026-08-03-15:20:
|
||||
U4 Slice B peels createWorktree into worktree-create-outer.ts / worktree-create-conflict.ts;
|
||||
worktree-acquisition lives under worktree/. Source-scan surfaces must follow the peels.
|
||||
*/
|
||||
const executorSource = readFileSync(fileURLToPath(new URL("../executor.ts", import.meta.url)), "utf8");
|
||||
/*
|
||||
FNXC:CodeOrganization 2026-08-15-19:10:
|
||||
Later executor peel waves moved the createWorktree facade off the thin executor.ts shell into
|
||||
executor/task-executor-worktree-pure-facades.ts (one-line delegation to createWorktreeImpl).
|
||||
*/
|
||||
const executorSource = readFileSync(fileURLToPath(new URL("../executor/task-executor-worktree-pure-facades.ts", import.meta.url)), "utf8");
|
||||
const createOuterSource = readFileSync(fileURLToPath(new URL("../executor/worktree-create-outer.ts", import.meta.url)), "utf8");
|
||||
const createConflictSource = readFileSync(fileURLToPath(new URL("../executor/worktree-create-conflict.ts", import.meta.url)), "utf8");
|
||||
const acquisitionSource = readFileSync(fileURLToPath(new URL("../worktree/worktree-acquisition.ts", import.meta.url)), "utf8");
|
||||
@@ -131,13 +136,20 @@ describe("TaskExecutor primary-checkout worktree invariant", () => {
|
||||
*/
|
||||
const executorFacade = sourceRegion(
|
||||
executorSource,
|
||||
"private async createWorktree(",
|
||||
"private async removeOwnWorktreeWithReconcile(",
|
||||
"protected async createWorktree(",
|
||||
"disposeStoreLifecycleDisposers(",
|
||||
);
|
||||
// Full peeled modules: createWorktree is not first in worktree-create-outer.ts.
|
||||
const outerImpl = createOuterSource;
|
||||
const conflictImpl = createConflictSource;
|
||||
const acquisition = sourceRegion(acquisitionSource, "const createWorktreeImpl = createWorktree", "const logConfiguredCopyFileResults");
|
||||
/*
|
||||
FNXC:CodeOrganization 2026-08-15-19:10:
|
||||
Acquisition's creation slice was reshaped: `createWorktreeImpl` is no longer a bare alias of the
|
||||
injected createWorktree — it now wraps `createWorktreeWithoutReservation` (which owns the
|
||||
`backend.create(` call) with path-reservation handling. Scan from the reservation-free creator
|
||||
so both creation surfaces stay inside the guarded region.
|
||||
*/
|
||||
const acquisition = sourceRegion(acquisitionSource, "const createWorktreeWithoutReservation = async (", "const logConfiguredCopyFileResults");
|
||||
const mergerReacquire = sourceRegion(mergerSource, "const reacquireReuseIntegrationWorktree = async", "// 3b. Ensure rootDir is based on the resolved integration target before merging.");
|
||||
|
||||
expect(executorFacade).toContain("createWorktreeImpl");
|
||||
|
||||
@@ -218,6 +218,16 @@ guards (86 -> 89 on #2883).
|
||||
function isDependencySatisfiedWithoutWorkflowMetadata(column: string): boolean {
|
||||
return column === "done" || column === "archived" || column === "in-review";
|
||||
}
|
||||
/*
|
||||
DELIBERATE-LITERAL — legacy complete-lane fallback for the FN-9056 orphaned-workspace-worktree
|
||||
sweep (FNXC:Workspace 2026-08-15-19:10). The sweep unions the workflow-resolved complete columns
|
||||
with the legacy `done` id so a row parked under the default lifecycle before its workflow could be
|
||||
read still qualifies for the complete lane. Hoisted so the marker sits in the declaration's leading
|
||||
comments, which is where the census looks.
|
||||
*/
|
||||
function isLegacyCompleteColumnForWorkspaceTeardown(column: string): boolean {
|
||||
return column === "done";
|
||||
}
|
||||
const worktreeMetadataReconcileLog = createLogger("worktree-metadata-reconcile");
|
||||
/*
|
||||
FNXC:EngineDiagnostics 2026-07-26-10:25:
|
||||
@@ -10703,7 +10713,7 @@ const movedTask = await this.store.moveTask(task.id, completeLane);
|
||||
|| this.isWorkspaceTaskLive(task).live
|
||||
|| await this.options.isMergePending?.(task.id) === true
|
||||
|| this.options.getActiveMergeTaskId?.() === task.id) continue;
|
||||
if (completeColumns.has(task.column) || task.column === "done") { candidates.push({ task, lane: "complete" }); continue; }
|
||||
if (completeColumns.has(task.column) || isLegacyCompleteColumnForWorkspaceTeardown(task.column)) { candidates.push({ task, lane: "complete" }); continue; }
|
||||
const lane: Lane | null = task.deletedAt ? "soft-deleted" : task.status === "failed" ? "failed" : null;
|
||||
if (!lane) continue;
|
||||
const touched = Math.max(Date.parse(task.columnMovedAt ?? "") || 0, Date.parse(task.updatedAt ?? "") || 0, Date.parse(task.deletedAt ?? "") || 0);
|
||||
|
||||
41
scripts/__tests__/lifecycle-census-mailbox-tab.test.mjs
Normal file
41
scripts/__tests__/lifecycle-census-mailbox-tab.test.mjs
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
FNXC:LifecycleColumnCensus 2026-08-13-21:58:
|
||||
Mailbox folder tabs reuse the word `archived`. A bare `activeTab === "archived"` must stay a
|
||||
column-guard hit so a genuine lifecycle comparison that happens to use that variable name still
|
||||
fails `--strict`. The mailbox sites mark a helper with DELIBERATE-LITERAL instead of a global
|
||||
receiver exemption.
|
||||
*/
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { findComparisons } from "../lib/lifecycle-column-census-ast.mjs";
|
||||
|
||||
test("a bare activeTab archived comparison stays in the column backlog", () => {
|
||||
const findings = findComparisons(
|
||||
"t.ts",
|
||||
'if (activeTab === "archived") loadArchivedInbox();',
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
findings.map(({ columnId, receiver, kind }) => ({ columnId, receiver, kind })),
|
||||
[{ columnId: "archived", receiver: "activeTab", kind: "column" }],
|
||||
);
|
||||
});
|
||||
|
||||
test("a mailbox archived-tab helper with DELIBERATE-LITERAL is not backlog", () => {
|
||||
const findings = findComparisons(
|
||||
"MailboxView.tsx",
|
||||
`/*
|
||||
DELIBERATE-LITERAL — mailbox folder tab, not a board column.
|
||||
*/
|
||||
function isMailboxArchivedTab(tab) {
|
||||
return tab === "archived";
|
||||
}
|
||||
`,
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
findings.map(({ columnId, receiver, kind }) => ({ columnId, receiver, kind })),
|
||||
[{ columnId: "archived", receiver: "tab", kind: "deliberate" }],
|
||||
);
|
||||
});
|
||||
@@ -6,6 +6,7 @@
|
||||
"packages/dashboard/app/components/TaskContextMenu.tsx\u0000in-review": 3,
|
||||
"packages/engine/src/scheduler.ts\u0000in-progress": 3,
|
||||
"packages/engine/src/scheduler.ts\u0000in-review": 3,
|
||||
"packages/engine/src/self-healing.ts\u0000done": 3,
|
||||
"packages/engine/src/self-healing.ts\u0000in-review": 3,
|
||||
"packages/core/src/agents/live-agent-count.ts\u0000in-progress": 2,
|
||||
"packages/core/src/agents/live-agent-count.ts\u0000in-review": 2,
|
||||
@@ -32,7 +33,6 @@
|
||||
"packages/engine/src/merge/auto-merge-finalization.ts\u0000done": 2,
|
||||
"packages/engine/src/scheduler.ts\u0000archived": 2,
|
||||
"packages/engine/src/scheduler.ts\u0000done": 2,
|
||||
"packages/engine/src/self-healing.ts\u0000done": 2,
|
||||
"packages/engine/src/triage.ts\u0000triage": 2,
|
||||
"plugins/fusion-plugin-reports/src/store/report-store.ts\u0000archived": 2,
|
||||
"packages/cli/src/commands/task.ts\u0000archived": 1,
|
||||
@@ -67,12 +67,15 @@
|
||||
"packages/core/src/tasks/task-intake-owner-resolver.ts\u0000todo": 1,
|
||||
"packages/core/src/tasks/task-move-disposer.ts\u0000in-progress": 1,
|
||||
"packages/core/src/tasks/task-move-disposer.ts\u0000todo": 1,
|
||||
"packages/core/src/tasks/workspace-lease-types.ts\u0000done": 1,
|
||||
"packages/dashboard/app/components/command-center/liveSnapshotMetrics.ts\u0000in-progress": 1,
|
||||
"packages/dashboard/app/components/command-center/MissionControlPanel.tsx\u0000done": 1,
|
||||
"packages/dashboard/app/components/command-center/MissionControlPanel.tsx\u0000in-review": 1,
|
||||
"packages/dashboard/app/components/command-center/MissionControlPanel.tsx\u0000todo": 1,
|
||||
"packages/dashboard/app/components/DocumentsView.tsx\u0000archived": 1,
|
||||
"packages/dashboard/app/components/DocumentsView.tsx\u0000done": 1,
|
||||
"packages/dashboard/app/components/MailboxModal.tsx\u0000archived": 1,
|
||||
"packages/dashboard/app/components/MailboxView.tsx\u0000archived": 1,
|
||||
"packages/dashboard/app/components/MissionManager.tsx\u0000archived": 1,
|
||||
"packages/dashboard/app/components/RoutineEditor.tsx\u0000triage": 1,
|
||||
"packages/dashboard/app/components/ScheduleForm.tsx\u0000triage": 1,
|
||||
|
||||
Reference in New Issue
Block a user