zob-harness 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.pi/agents/harness-factory-designer.md +47 -0
- package/.pi/agents/harness-intake-oracle.md +54 -0
- package/.pi/agents/harness-intake-orchestrator.md +40 -0
- package/.pi/agents/harness-interpreter.md +45 -0
- package/.pi/agents/harness-session-miner.md +45 -0
- package/.pi/agents/harness-skill-command-analyst.md +43 -0
- package/.pi/agents/harness-source-cartographer.md +40 -0
- package/.pi/agents/harness-workflow-pattern-miner.md +40 -0
- package/.pi/agents/zob-team-architect.md +45 -0
- package/.pi/capabilities/zob-public-runtime-capabilities.json +40 -14
- package/.pi/extensions/zob-harness/AGENTS.md +21 -21
- package/.pi/extensions/zob-harness/src/AGENTS.md +20 -20
- package/.pi/extensions/zob-harness/src/domains/autonomy/interactive-autonomy.ts +14 -14
- package/.pi/extensions/zob-harness/src/domains/coms/coms-v2/registry.ts +44 -1
- package/.pi/extensions/zob-harness/src/domains/coms/coms-v2/zpeer.ts +32 -4
- package/.pi/extensions/zob-harness/src/domains/coms/mission-control.ts +4 -1
- package/.pi/extensions/zob-harness/src/domains/delegation/output-contracts.ts +37 -37
- package/.pi/extensions/zob-harness/src/domains/factory/agentic-plan.ts +1 -1
- package/.pi/extensions/zob-harness/src/domains/goal/goal-todo-types.ts +20 -0
- package/.pi/extensions/zob-harness/src/domains/goal/goal-todos.ts +60 -5
- package/.pi/extensions/zob-harness/src/domains/project-dna/project-dna.ts +1 -1
- package/.pi/extensions/zob-harness/src/domains/telemetry/chronicle.ts +1 -1
- package/.pi/extensions/zob-harness/src/factory/AGENTS.md +14 -14
- package/.pi/extensions/zob-harness/src/orchestration/AGENTS.md +13 -13
- package/.pi/extensions/zob-harness/src/runtime/AGENTS.md +14 -14
- package/.pi/extensions/zob-harness/src/runtime/commands.ts +2 -2
- package/.pi/extensions/zob-harness/src/runtime/events.ts +6 -5
- package/.pi/extensions/zob-harness/src/runtime/goal-runtime.ts +376 -0
- package/.pi/extensions/zob-harness/src/runtime/mode-intent.ts +10 -10
- package/.pi/extensions/zob-harness/src/runtime/plan-capture.ts +4 -4
- package/.pi/extensions/zob-harness/src/runtime/state.ts +3 -3
- package/.pi/extensions/zob-harness/src/runtime/tools-delegation.ts +57 -7
- package/.pi/extensions/zob-harness/src/runtime/widget.ts +2 -2
- package/.pi/extensions/zob-harness/src/topology/AGENTS.md +12 -12
- package/.pi/extensions/zob-harness/src/utils/AGENTS.md +12 -12
- package/.pi/factories/harness-intake-agent-team/README.md +79 -0
- package/.pi/factories/harness-intake-agent-team/batch-manifest.json +19 -0
- package/.pi/factories/harness-intake-agent-team/factory.json +127 -0
- package/.pi/factories/harness-intake-agent-team/pilot-manifest.json +20 -0
- package/.pi/factories/harness-intake-agent-team/schemas/artifact-contracts.schema.json +28 -0
- package/.pi/factories/harness-intake-agent-team/schemas/factory-candidates.schema.json +27 -0
- package/.pi/factories/harness-intake-agent-team/schemas/inferred-run-spec.schema.json +44 -0
- package/.pi/factories/harness-intake-agent-team/schemas/sources-index.schema.json +30 -0
- package/.pi/factories/harness-intake-agent-team/schemas/team-candidates.schema.json +28 -0
- package/.pi/factories/harness-intake-agent-team/schemas/validation.schema.json +17 -0
- package/.pi/factories/harness-intake-agent-team/smoke-manifest.json +25 -0
- package/.pi/factories/harness-intake-agent-team/templates/generated-agent.md +22 -0
- package/.pi/factories/harness-intake-agent-team/templates/kickoff.md +13 -0
- package/.pi/factories/harness-intake-agent-team/validators/validate-no-secrets.mjs +20 -0
- package/.pi/factories/harness-intake-agent-team/validators/validate-quarantine.mjs +20 -0
- package/.pi/factories/harness-intake-agent-team/validators/validate-run.mjs +9 -0
- package/.pi/skills/harness-intake/SKILL.md +100 -0
- package/.pi/skills/zob-agentic-spec-team/SKILL.md +4 -1
- package/.pi/skills/zob-coms-safety/SKILL.md +17 -2
- package/.pi/skills/zob-coms-v2-live/SKILL.md +15 -3
- package/.pi/skills/zob-factory/SKILL.md +21 -0
- package/.pi/skills/zob-goal-todo-tree/SKILL.md +49 -4
- package/.pi/skills/zob-harness/SKILL.md +14 -0
- package/.pi/skills/zob-owner-pool-drill-writer/SKILL.md +4 -4
- package/.pi/skills/zob-owner-pool-launcher/SKILL.md +7 -7
- package/.pi/skills/zob-zagent-creator/SKILL.md +13 -3
- package/.pi/teams/harness-intake-team.json +114 -0
- package/.pi/zagents/agent-factory-pacman-chief.json +22 -0
- package/.pi/zagents/agent-factory-pacman-engine-builder.json +21 -0
- package/.pi/zagents/agent-factory-pacman-frontend-builder.json +21 -0
- package/.pi/zagents/agent-factory-pacman-game-architect.json +21 -0
- package/.pi/zagents/agent-factory-pacman-game-designer.json +21 -0
- package/.pi/zagents/agent-factory-pacman-qa-oracle.json +21 -0
- package/.pi/zagents/prompts/agent-factory-pacman-chief.md +53 -0
- package/.pi/zagents/prompts/agent-factory-pacman-engine-builder.md +41 -0
- package/.pi/zagents/prompts/agent-factory-pacman-frontend-builder.md +40 -0
- package/.pi/zagents/prompts/agent-factory-pacman-game-architect.md +41 -0
- package/.pi/zagents/prompts/agent-factory-pacman-game-designer.md +43 -0
- package/.pi/zagents/prompts/agent-factory-pacman-qa-oracle.md +51 -0
- package/.pi/zteams/agent-factory-pacman-multiplayer-runtime.mjs +384 -0
- package/.pi/zteams/agent-factory-pacman-multiplayer.json +42 -0
- package/.pi/zteams/agent-factory-pacman-multiplayer.tmux.sh +256 -0
- package/.pi/zteams/templates/agent-factory-pacman-chief-kickoff.template.md +71 -0
- package/.pi/zteams/templates/agent-factory-pacman-worker-kickoff.template.md +59 -0
- package/README.md +264 -109
- package/SOURCE_INDEX.md +4 -0
- package/examples/agent-factory-mission-control/AGENTS.md +10 -0
- package/examples/agent-factory-mission-control/README.md +17 -0
- package/examples/agent-factory-mission-control/apps/api/AGENTS.md +3 -0
- package/examples/agent-factory-mission-control/apps/dashboard/AGENTS.md +3 -0
- package/examples/agent-factory-mission-control/mission.md +3 -0
- package/examples/agent-factory-mission-control/output-contract.md +3 -0
- package/examples/agent-factory-mission-control/packages/domain/AGENTS.md +3 -0
- package/examples/agent-factory-mission-control/packages/snapshot-reader/AGENTS.md +3 -0
- package/examples/agent-factory-pacman-multiplayer/AGENTS.md +27 -0
- package/examples/agent-factory-pacman-multiplayer/README.md +84 -0
- package/examples/agent-factory-pacman-multiplayer/mission.md +43 -0
- package/examples/agent-factory-pacman-multiplayer/output-contract.md +58 -0
- package/examples/agent-factory-tmux-comms/README.md +146 -0
- package/examples/agent-factory-tmux-comms/chief-kickoff.template.md +54 -0
- package/examples/agent-factory-tmux-comms/simple-agent-factory.team.json +92 -0
- package/examples/agent-factory-tmux-comms/simple-agent-factory.tmux.sh +248 -0
- package/examples/agent-factory-tmux-comms/worker-kickoff.template.md +43 -0
- package/examples/harness-intake-fixtures/claude-code-mini/.claude/agents/reviewer.md +7 -0
- package/examples/harness-intake-fixtures/claude-code-mini/.claude/agents/specifier.md +7 -0
- package/examples/harness-intake-fixtures/claude-code-mini/.claude/commands/review.md +7 -0
- package/examples/harness-intake-fixtures/claude-code-mini/.claude/commands/spec.md +9 -0
- package/examples/harness-intake-fixtures/claude-code-mini/.claude/sessions/session-001.md +10 -0
- package/examples/harness-intake-fixtures/claude-code-mini/CLAUDE.md +22 -0
- package/package.json +35 -3
- package/scripts/README.md +7 -1
- package/scripts/goal-todo/child-goal-ref-smoke.mjs +2 -0
- package/scripts/goal-todo/handoff-static-smoke.mjs +32 -0
- package/scripts/harness-intake/infer-run-spec.mjs +34 -0
- package/scripts/harness-intake/launch.mjs +32 -0
- package/scripts/harness-intake/lib.mjs +1521 -0
- package/scripts/harness-intake/scan-sources.mjs +30 -0
- package/scripts/harness-intake/tmux-launch.mjs +48 -0
- package/scripts/harness-intake/validate-run.mjs +28 -0
- package/scripts/package-surface/validate-capability-refs.mjs +112 -0
- package/scripts/project-dna/query/query-context.mjs +1 -1
- package/scripts/project-dna/query/query-steward.mjs +1 -1
- package/scripts/zagent-static-smoke.mjs +1 -1
- package/scripts/zpeer-local-e2e-smoke.mjs +6 -0
|
@@ -249,10 +249,10 @@ function patternMatchesAny(text: string, patterns: string[]): boolean {
|
|
|
249
249
|
});
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
const SECRET_ACCESS_VERB_PATTERN = /\b(read|cat|open|inspect|print|show|copy|extract|use
|
|
253
|
-
const SECRET_ACCESS_CONTEXT_PATTERN = /\b(secret|token|api[_ -]?key|private\s+key|ssh\s+key|credential|
|
|
254
|
-
const NEGATIVE_SAFETY_DIRECTIVE_PATTERN = /\b(do not|don't|dont|never|must not|mustn't|avoid|forbidden|denylist|deny list|blocked|without|no\s+secrets
|
|
255
|
-
const CONTRAST_OR_EXCEPTION_PATTERN = /\b(but|however|except|unless
|
|
252
|
+
const SECRET_ACCESS_VERB_PATTERN = /\b(read|cat|open|inspect|print|show|copy|extract|use)\b/i;
|
|
253
|
+
const SECRET_ACCESS_CONTEXT_PATTERN = /\b(secret|token|api[_ -]?key|private\s+key|ssh\s+key|credential|identifier)\b.{0,80}\b(read|show|print|copy|use)\b/i;
|
|
254
|
+
const NEGATIVE_SAFETY_DIRECTIVE_PATTERN = /\b(do not|don't|dont|never|must not|mustn't|avoid|forbidden|denylist|deny list|blocked|without|no\s+secrets?)\b/i;
|
|
255
|
+
const CONTRAST_OR_EXCEPTION_PATTERN = /\b(but|however|except|unless)\b/i;
|
|
256
256
|
|
|
257
257
|
function isNegativeSecretSafetyLine(line: string, policy: InteractiveAutonomyPolicy): boolean {
|
|
258
258
|
const trimmed = line.trim();
|
|
@@ -278,8 +278,8 @@ function secretAccessRequested(text: string, policy: InteractiveAutonomyPolicy):
|
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
function productionApplyRequested(text: string): boolean {
|
|
281
|
-
return /\b(deploy|release|ship|apply|write|push|publish
|
|
282
|
-
|| /\b(prod|production|live)\b.{0,80}\b(deploy|release|ship|apply|write|push|publish
|
|
281
|
+
return /\b(deploy|release|ship|apply|write|push|publish)\b.{0,80}\b(prod|production|live)\b/i.test(text)
|
|
282
|
+
|| /\b(prod|production|live)\b.{0,80}\b(deploy|release|ship|apply|write|push|publish)\b/i.test(text);
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
function extractTargetPaths(text: string): string[] {
|
|
@@ -360,14 +360,14 @@ export function scoreMissionReadiness(text: string, options: { mode: Interactive
|
|
|
360
360
|
const clarificationCodes: string[] = [];
|
|
361
361
|
const safetyGateCodes = ["no_secrets", "no_destructive_commands", "no_production_apply", "no_global_production_claim", "validation_required"];
|
|
362
362
|
|
|
363
|
-
const hasActionVerb = /\b(implement|
|
|
364
|
-
const hasAcceptanceLanguage = /\b(acceptance|criteria|
|
|
365
|
-
const hasTestabilityLanguage = /\b(test|tests|smoke|npm run|check|typecheck|validation|validate|oracle|proof|
|
|
366
|
-
const asksClarificationOnly = /\b(explain|
|
|
363
|
+
const hasActionVerb = /\b(implement|build|create|add|fix|modify|change|update|refactor|wire|validate|test|smoke|audit|review|document|write|edit|make)\b/i.test(raw);
|
|
364
|
+
const hasAcceptanceLanguage = /\b(acceptance|criteria|must|must not|validation|validate|test|smoke|proof|evidence|oracle|done when|definition of done)\b/i.test(raw);
|
|
365
|
+
const hasTestabilityLanguage = /\b(test|tests|smoke|npm run|check|typecheck|validation|validate|oracle|proof|assert|verify)\b/i.test(raw);
|
|
366
|
+
const asksClarificationOnly = /\b(explain|why|question|help|status|show)\b/i.test(raw) && !hasActionVerb;
|
|
367
367
|
const destructiveRequested = policy.safety.blockDestructiveCommands && patternMatchesAny(raw, policy.safety.destructivePatterns);
|
|
368
368
|
const secretRequested = policy.safety.blockSecretAccess && secretAccessRequested(raw, policy);
|
|
369
369
|
const productionRequested = policy.safety.blockProductionApply && productionApplyRequested(raw);
|
|
370
|
-
const globalClaimRequested = /\b(100%|global|production[- ]?wide|full production|prod)\b.{0,80}\b(claim|declare|
|
|
370
|
+
const globalClaimRequested = /\b(100%|global|production[- ]?wide|full production|prod)\b.{0,80}\b(claim|declare|prove|certify|ready|autonomous|autonomy)\b/i.test(raw);
|
|
371
371
|
|
|
372
372
|
if (!raw) blockerCodes.push("user_input_missing");
|
|
373
373
|
if (destructiveRequested) blockerCodes.push("destructive_action_requested");
|
|
@@ -376,9 +376,9 @@ export function scoreMissionReadiness(text: string, options: { mode: Interactive
|
|
|
376
376
|
if (globalClaimRequested) clarificationCodes.push("global_production_claim_requires_fresh_oracle_proof");
|
|
377
377
|
|
|
378
378
|
const clarity = raw.length >= 80 && hasActionVerb ? 0.9 : raw.length >= 35 && hasActionVerb ? 0.72 : hasActionVerb ? 0.55 : asksClarificationOnly ? 0.35 : raw.length > 0 ? 0.25 : 0;
|
|
379
|
-
const acceptanceCriteria = hasAcceptanceLanguage ? 0.8 : /\b(done|
|
|
380
|
-
const targetPaths = targetPathRefs.length > 0 ? 0.85 : /\b(repo|project|harness|pi|extension|codebase
|
|
381
|
-
const testability = hasTestabilityLanguage ? 0.85 : /\b(works|
|
|
379
|
+
const acceptanceCriteria = hasAcceptanceLanguage ? 0.8 : /\b(done|complete|finished|deliverable)\b/i.test(raw) ? 0.5 : 0.15;
|
|
380
|
+
const targetPaths = targetPathRefs.length > 0 ? 0.85 : /\b(repo|project|harness|pi|extension|codebase)\b/i.test(raw) ? 0.45 : 0.15;
|
|
381
|
+
const testability = hasTestabilityLanguage ? 0.85 : /\b(works|ready|ok)\b/i.test(raw) ? 0.35 : 0.15;
|
|
382
382
|
const risk: MissionRiskLevel = blockerCodes.length > 0 || productionRequested ? "high" : /\b(network|browser|cloud|api|deploy|publish|commit|database|db|payment|auth)\b/i.test(normalized) ? "medium" : "low";
|
|
383
383
|
const safety = risk === "high" ? 0 : risk === "medium" ? 0.55 : 1;
|
|
384
384
|
const signals: MissionReadinessSignals = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
|
|
@@ -10,6 +10,8 @@ import { readZobComsV2Policy, zobComsRegistryEnabled } from "./policy.js";
|
|
|
10
10
|
import type { ZobLivePeerCard, ZobLivePeerStatus, ZobLiveRegistrySnapshot } from "./types.js";
|
|
11
11
|
|
|
12
12
|
const FORBIDDEN_PERSISTED_KEYS = new Set(["body", "task", "prompt", "output", "content", "message", "rationale", "text", "diff", "patch"]);
|
|
13
|
+
const DEFAULT_OFFLINE_PEER_RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
14
|
+
const MIN_OFFLINE_PEER_RETENTION_MS = 5 * 60 * 1000;
|
|
13
15
|
|
|
14
16
|
function registryRoot(): { path: string; kind: "user_runtime" | "env_override" } {
|
|
15
17
|
const override = process.env.ZOB_COMS_REGISTRY_ROOT;
|
|
@@ -54,6 +56,19 @@ function readPeerCardsFromAgentsDir(dir: string, nowMs: number, teamName?: strin
|
|
|
54
56
|
.map((peer) => ({ ...peer, status: derivePeerStatus(peer, nowMs) }));
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
function boundedOfflinePeerRetentionMs(value: number | undefined): number {
|
|
60
|
+
const env = Number.parseInt(process.env.ZOB_COMS_OFFLINE_PEER_RETENTION_MS ?? "", 10);
|
|
61
|
+
const raw = typeof value === "number" && Number.isFinite(value) ? value : Number.isFinite(env) ? env : DEFAULT_OFFLINE_PEER_RETENTION_MS;
|
|
62
|
+
return Math.max(MIN_OFFLINE_PEER_RETENTION_MS, Math.floor(raw));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function offlinePeerExpired(peer: ZobLivePeerCard, nowMs: number, retentionMs: number): boolean {
|
|
66
|
+
if (derivePeerStatus(peer, nowMs) !== "offline") return false;
|
|
67
|
+
const heartbeatMs = Date.parse(peer.heartbeatAt);
|
|
68
|
+
if (!Number.isFinite(heartbeatMs)) return true;
|
|
69
|
+
return nowMs - heartbeatMs >= Math.max(peer.offlineAfterMs, retentionMs);
|
|
70
|
+
}
|
|
71
|
+
|
|
57
72
|
function allProjectAgentsDirs(): string[] {
|
|
58
73
|
const root = registryRoot();
|
|
59
74
|
const projectsDir = join(root.path, "projects");
|
|
@@ -94,6 +109,34 @@ function derivePeerStatus(peer: ZobLivePeerCard, nowMs: number): ZobLivePeerStat
|
|
|
94
109
|
return "online";
|
|
95
110
|
}
|
|
96
111
|
|
|
112
|
+
export function pruneExpiredZobLivePeers(repoRoot: string, input: { teamName?: string; nowMs?: number; retentionMs?: number } = {}): { schema: "zob.live-registry-prune.v1"; pruned: number; retained: number; retentionMs: number; bodyStored: false } {
|
|
113
|
+
const { dir } = projectAgentsDir(repoRoot);
|
|
114
|
+
const nowMs = input.nowMs ?? Date.now();
|
|
115
|
+
const retentionMs = boundedOfflinePeerRetentionMs(input.retentionMs);
|
|
116
|
+
let pruned = 0;
|
|
117
|
+
let retained = 0;
|
|
118
|
+
if (!existsSync(dir)) return { schema: "zob.live-registry-prune.v1", pruned, retained, retentionMs, bodyStored: false };
|
|
119
|
+
for (const entry of readdirSync(dir).filter((name) => name.endsWith(".json"))) {
|
|
120
|
+
const filePath = join(dir, entry);
|
|
121
|
+
try {
|
|
122
|
+
const peer = parsePeerCard(JSON.parse(readFileSync(filePath, "utf8")) as unknown);
|
|
123
|
+
if (!peer || (input.teamName && peer.team !== input.teamName)) {
|
|
124
|
+
retained += 1;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (offlinePeerExpired(peer, nowMs, retentionMs)) {
|
|
128
|
+
unlinkSync(filePath);
|
|
129
|
+
pruned += 1;
|
|
130
|
+
} else {
|
|
131
|
+
retained += 1;
|
|
132
|
+
}
|
|
133
|
+
} catch {
|
|
134
|
+
retained += 1;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return { schema: "zob.live-registry-prune.v1", pruned, retained, retentionMs, bodyStored: false };
|
|
138
|
+
}
|
|
139
|
+
|
|
97
140
|
export function writeZobLivePeerCard(repoRoot: string, peer: ZobLivePeerCard): ZobLivePeerCard {
|
|
98
141
|
if (hasForbiddenPersistedKey(peer)) throw new Error("Refusing to persist ZOB live peer card with forbidden body-like keys");
|
|
99
142
|
if (peer.bodyStored !== false) throw new Error("ZOB live peer card bodyStored must be false");
|
|
@@ -26,6 +26,9 @@ export interface ZpeerRoomSummary {
|
|
|
26
26
|
stale: number;
|
|
27
27
|
offline: number;
|
|
28
28
|
aliases: string[];
|
|
29
|
+
onlineAliases: string[];
|
|
30
|
+
staleAliases: string[];
|
|
31
|
+
offlineAliases: string[];
|
|
29
32
|
duplicateAliases: string[];
|
|
30
33
|
membershipCount?: number;
|
|
31
34
|
localOnly: true;
|
|
@@ -248,9 +251,15 @@ function peersInRoom(repoRoot: string, roomId: string): ZpeerRoomPeer[] {
|
|
|
248
251
|
|
|
249
252
|
function buildZpeerRoomSummaryFromPeers(projectId: string, self: ZobLivePeerCard | undefined, roomId: string, peers: ZpeerRoomPeer[]): ZpeerRoomSummary {
|
|
250
253
|
const counts: Record<ZobLivePeerStatus, number> = { online: 0, stale: 0, offline: 0 };
|
|
254
|
+
const statusAliases: Record<ZobLivePeerStatus, string[]> = { online: [], stale: [], offline: [] };
|
|
251
255
|
const aliases = peers.map((entry) => entry.membership.alias).sort();
|
|
252
|
-
for (const entry of peers)
|
|
253
|
-
|
|
256
|
+
for (const entry of peers) {
|
|
257
|
+
const status = zpeerReachableStatus(entry.peer);
|
|
258
|
+
counts[status] += 1;
|
|
259
|
+
statusAliases[status].push(entry.membership.alias);
|
|
260
|
+
}
|
|
261
|
+
const onlineAliases = statusAliases.online.sort();
|
|
262
|
+
const duplicateAliases = onlineAliases.filter((alias, index) => onlineAliases.indexOf(alias) !== index).filter((alias, index, all) => all.indexOf(alias) === index);
|
|
254
263
|
return {
|
|
255
264
|
schema: "zob.zpeer-room-summary.v1",
|
|
256
265
|
projectId,
|
|
@@ -261,6 +270,9 @@ function buildZpeerRoomSummaryFromPeers(projectId: string, self: ZobLivePeerCard
|
|
|
261
270
|
stale: counts.stale,
|
|
262
271
|
offline: counts.offline,
|
|
263
272
|
aliases,
|
|
273
|
+
onlineAliases,
|
|
274
|
+
staleAliases: statusAliases.stale.sort(),
|
|
275
|
+
offlineAliases: statusAliases.offline.sort(),
|
|
264
276
|
duplicateAliases,
|
|
265
277
|
membershipCount: self ? zpeerMembershipsForPeer(self).length : undefined,
|
|
266
278
|
localOnly: true,
|
|
@@ -471,8 +483,24 @@ export async function sendZpeerPrompt(repoRoot: string, self: ZobLivePeerCard, t
|
|
|
471
483
|
const candidates = peersInRoom(repoRoot, roomId).filter((entry) => entry.membership.alias === targetAlias && entry.peer.sessionHash !== self.sessionHash);
|
|
472
484
|
if (targetAlias === senderAlias) return finish("attempt", { status: "blocked", reason: "cannot send to self", targetAlias, taskHash, bodyStored: false });
|
|
473
485
|
if (candidates.length === 0) return finish("attempt", { status: "blocked", reason: `peer @${targetAlias} not found in room '${roomId}'`, targetAlias, taskHash, bodyStored: false }, 0);
|
|
474
|
-
|
|
475
|
-
|
|
486
|
+
let liveCandidates = candidates.filter((entry) => zpeerReachableStatus(entry.peer) === "online");
|
|
487
|
+
if (liveCandidates.length > 1) {
|
|
488
|
+
const responsiveCandidates: ZpeerRoomPeer[] = [];
|
|
489
|
+
for (const entry of liveCandidates) {
|
|
490
|
+
if (await peerRespondsToAliasPing(entry.peer)) {
|
|
491
|
+
responsiveCandidates.push(entry);
|
|
492
|
+
} else {
|
|
493
|
+
try { writeZobLivePeerCard(repoRoot, { ...entry.peer, heartbeatAt: new Date().toISOString(), status: "offline" }); } catch { /* best-effort ghost alias release */ }
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
liveCandidates = responsiveCandidates;
|
|
497
|
+
}
|
|
498
|
+
if (liveCandidates.length === 0) {
|
|
499
|
+
const statuses = [...new Set(candidates.map((entry) => zpeerReachableStatus(entry.peer)))].sort().join("/") || "offline";
|
|
500
|
+
return finish("attempt", { status: "blocked", reason: `peer @${targetAlias} is ${statuses}`, targetAlias, taskHash, bodyStored: false }, candidates.length);
|
|
501
|
+
}
|
|
502
|
+
if (liveCandidates.length > 1) return finish("attempt", { status: "blocked", reason: `duplicate live alias @${targetAlias} in room '${roomId}'`, targetAlias, taskHash, bodyStored: false }, liveCandidates.length);
|
|
503
|
+
const target = liveCandidates[0];
|
|
476
504
|
const targetReachableStatus = zpeerReachableStatus(target.peer);
|
|
477
505
|
if (targetReachableStatus !== "online") return finish("attempt", { status: "blocked", reason: `peer @${targetAlias} is ${targetReachableStatus}`, targetAlias, taskHash, bodyStored: false }, 1);
|
|
478
506
|
const topologyBlocker = validateZpeerTopology(repoRoot, self, target.peer, roomId, senderAlias, target.membership.alias);
|
|
@@ -63,6 +63,7 @@ function summarizeZpeerRooms(peers: Array<Record<string, unknown>>): Array<Recor
|
|
|
63
63
|
}
|
|
64
64
|
return [...rooms.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([roomId, roomPeers]) => {
|
|
65
65
|
const aliases = roomPeers.map((entry) => entry.alias).filter((alias): alias is string => Boolean(alias)).sort();
|
|
66
|
+
const onlineAliases = roomPeers.filter((entry) => entry.peer.status === "online").map((entry) => entry.alias).filter((alias): alias is string => Boolean(alias)).sort();
|
|
66
67
|
const sessionHashes = roomPeers.map((entry) => typeof entry.peer.sessionHash === "string" ? entry.peer.sessionHash : undefined).filter((sessionHash): sessionHash is string => Boolean(sessionHash));
|
|
67
68
|
return {
|
|
68
69
|
schema: "zob.zpeer-room-summary.v1",
|
|
@@ -73,7 +74,9 @@ function summarizeZpeerRooms(peers: Array<Record<string, unknown>>): Array<Recor
|
|
|
73
74
|
stale: roomPeers.filter((entry) => entry.peer.status === "stale").length,
|
|
74
75
|
offline: roomPeers.filter((entry) => entry.peer.status === "offline").length,
|
|
75
76
|
aliasHashes: aliases.map((alias) => sha256(alias)),
|
|
76
|
-
|
|
77
|
+
onlineAliasHashes: onlineAliases.map((alias) => sha256(alias)),
|
|
78
|
+
duplicateAliasHashes: onlineAliases.filter((alias, index) => onlineAliases.indexOf(alias) !== index).filter((alias, index, all) => all.indexOf(alias) === index).map((alias) => sha256(alias)),
|
|
79
|
+
duplicateAliasScope: "online_only",
|
|
77
80
|
localOnly: true,
|
|
78
81
|
networkEnabled: false,
|
|
79
82
|
bodyStored: false,
|
|
@@ -11,17 +11,17 @@ const COMMON_OUTPUT_REQUIREMENTS: OutputRequirement[] = [
|
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
13
|
name: "evidence",
|
|
14
|
-
pattern: "<evidence>|\\bevidence\\b|
|
|
14
|
+
pattern: "<evidence>|\\bevidence\\b|proof",
|
|
15
15
|
message: "Missing evidence/proof section",
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
name: "risks_blockers",
|
|
19
|
-
pattern: "<risks_blockers>|risks?/blockers?|risks?|blockers
|
|
19
|
+
pattern: "<risks_blockers>|risks?/blockers?|risks?|blockers?",
|
|
20
20
|
message: "Missing risks/blockers section",
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
name: "compliance",
|
|
24
|
-
pattern: "<compliance>|\\bcompliance\\b|
|
|
24
|
+
pattern: "<compliance>|\\bcompliance\\b|must not",
|
|
25
25
|
message: "Missing compliance line",
|
|
26
26
|
},
|
|
27
27
|
];
|
|
@@ -42,10 +42,10 @@ const OUTPUT_CONTRACTS: OutputContract[] = [
|
|
|
42
42
|
{ name: "literal_request", pattern: "<literal_request>|literal request|literal_request", message: "Explore output missing literal_request" },
|
|
43
43
|
{ name: "actual_need", pattern: "<actual_need>|actual need|actual_need", message: "Explore output missing actual_need" },
|
|
44
44
|
{ name: "success_looks_like", pattern: "<success_looks_like>|success looks like|success_looks_like", message: "Explore output missing success_looks_like" },
|
|
45
|
-
{ name: "files", pattern: "<files>|\\bfiles\\b
|
|
46
|
-
{ name: "answer", pattern: "<answer>|\\banswer\\b
|
|
47
|
-
{ name: "gaps", pattern: "<gaps>|\\bgaps\\b
|
|
48
|
-
{ name: "next_steps", pattern: "<next_steps>|next steps|next_steps
|
|
45
|
+
{ name: "files", pattern: "<files>|\\bfiles\\b", message: "Explore output missing files section" },
|
|
46
|
+
{ name: "answer", pattern: "<answer>|\\banswer\\b", message: "Explore output missing answer section" },
|
|
47
|
+
{ name: "gaps", pattern: "<gaps>|\\bgaps\\b", message: "Explore output missing gaps section" },
|
|
48
|
+
{ name: "next_steps", pattern: "<next_steps>|next steps|next_steps", message: "Explore output missing next_steps section" },
|
|
49
49
|
],
|
|
50
50
|
},
|
|
51
51
|
{
|
|
@@ -55,10 +55,10 @@ const OUTPUT_CONTRACTS: OutputContract[] = [
|
|
|
55
55
|
required: [
|
|
56
56
|
...COMMON_OUTPUT_REQUIREMENTS,
|
|
57
57
|
{ name: "scope", pattern: "scope|in-scope|out-of-scope|forbidden", message: "Plan output missing scope table" },
|
|
58
|
-
{ name: "assumptions", pattern: "assumptions
|
|
59
|
-
{ name: "implementation_slices", pattern: "implementation (steps|slices)|slices
|
|
58
|
+
{ name: "assumptions", pattern: "assumptions?", message: "Plan output missing assumptions" },
|
|
59
|
+
{ name: "implementation_slices", pattern: "implementation (steps|slices)|slices?", message: "Plan output missing implementation slices/steps" },
|
|
60
60
|
{ name: "validation_ladder", pattern: "validation ladder|validation", message: "Plan output missing validation ladder" },
|
|
61
|
-
{ name: "stop_conditions", pattern: "stop conditions
|
|
61
|
+
{ name: "stop_conditions", pattern: "stop conditions?", message: "Plan output missing stop conditions" },
|
|
62
62
|
{ name: "handoff_contract", pattern: "handoff|TASK:|EXPECTED OUTCOME:|MUST NOT", message: "Plan output missing implementer handoff contract" },
|
|
63
63
|
],
|
|
64
64
|
},
|
|
@@ -69,9 +69,9 @@ const OUTPUT_CONTRACTS: OutputContract[] = [
|
|
|
69
69
|
required: [
|
|
70
70
|
...COMMON_OUTPUT_REQUIREMENTS,
|
|
71
71
|
{ name: "gap_verdict", pattern: "gap verdict|SUFFICIENT|GAP|no change|no-change", message: "Implement output missing gap/no-change verdict" },
|
|
72
|
-
{ name: "changed_files", pattern: "changed files|
|
|
73
|
-
{ name: "verification_commands", pattern: "verification|
|
|
74
|
-
{ name: "results", pattern: "results?|
|
|
72
|
+
{ name: "changed_files", pattern: "changed files|no change|no-change", message: "Implement output missing changed files or no-change evidence" },
|
|
73
|
+
{ name: "verification_commands", pattern: "verification|commands?", message: "Implement output missing verification commands" },
|
|
74
|
+
{ name: "results", pattern: "results?|exit code|passed|failed", message: "Implement output missing command/results evidence" },
|
|
75
75
|
],
|
|
76
76
|
},
|
|
77
77
|
{
|
|
@@ -81,8 +81,8 @@ const OUTPUT_CONTRACTS: OutputContract[] = [
|
|
|
81
81
|
required: [
|
|
82
82
|
...COMMON_OUTPUT_REQUIREMENTS,
|
|
83
83
|
{ name: "verdict", pattern: "<verdict>\\s*(PASS|FAIL|WARN)\\s*</verdict>|\\b(PASS|FAIL|WARN)\\b", message: "Oracle output missing PASS/FAIL/WARN verdict" },
|
|
84
|
-
{ name: "confidence", pattern: "<confidence>|\\bconfidence\\b
|
|
85
|
-
{ name: "blocking_issues", pattern: "<blocking_issues>|blocking issues?|blockers
|
|
84
|
+
{ name: "confidence", pattern: "<confidence>|\\bconfidence\\b", message: "Oracle output missing confidence" },
|
|
85
|
+
{ name: "blocking_issues", pattern: "<blocking_issues>|blocking issues?|blockers?", message: "Oracle output missing blocking issues" },
|
|
86
86
|
{ name: "non_blocking_notes", pattern: "<non_blocking_notes>|non[-_ ]blocking", message: "Oracle output missing non-blocking notes" },
|
|
87
87
|
{ name: "no_ship", pattern: "<no_ship>|no_ship|no ship", message: "Oracle output missing no_ship decision" },
|
|
88
88
|
],
|
|
@@ -94,7 +94,7 @@ const OUTPUT_CONTRACTS: OutputContract[] = [
|
|
|
94
94
|
required: [
|
|
95
95
|
...COMMON_OUTPUT_REQUIREMENTS,
|
|
96
96
|
{ name: "verdict", pattern: "\\b(PASS|FAIL|WARN|INCONCLUSIVE)\\b|verdict", message: "QA output missing verification verdict" },
|
|
97
|
-
{ name: "command", pattern: "commands?|
|
|
97
|
+
{ name: "command", pattern: "commands?|cwd", message: "QA output missing command/cwd evidence" },
|
|
98
98
|
{ name: "exit_or_output", pattern: "exit code|output|stdout|stderr|important output", message: "QA output missing exit/output evidence" },
|
|
99
99
|
{ name: "reproduction", pattern: "reproduction|reproduce|steps", message: "QA output missing reproduction steps" },
|
|
100
100
|
],
|
|
@@ -106,8 +106,8 @@ const OUTPUT_CONTRACTS: OutputContract[] = [
|
|
|
106
106
|
required: [
|
|
107
107
|
...COMMON_OUTPUT_REQUIREMENTS,
|
|
108
108
|
{ name: "consensus", pattern: "<consensus>|\\bconsensus\\b", message: "Synthesis output missing consensus section" },
|
|
109
|
-
{ name: "conflicts", pattern: "<conflicts>|conflicts
|
|
110
|
-
{ name: "missing_evidence", pattern: "<missing_evidence>|missing_evidence|missing evidence
|
|
109
|
+
{ name: "conflicts", pattern: "<conflicts>|conflicts?", message: "Synthesis output missing conflicts section" },
|
|
110
|
+
{ name: "missing_evidence", pattern: "<missing_evidence>|missing_evidence|missing evidence", message: "Synthesis output missing missing_evidence section" },
|
|
111
111
|
{ name: "recommended_next_action", pattern: "<recommended_next_action>|recommended_next_action|recommended next action", message: "Synthesis output missing recommended_next_action section" },
|
|
112
112
|
{ name: "tasks_to_rerun", pattern: "<tasks_to_rerun>|tasks_to_rerun|tasks to rerun|rerun", message: "Synthesis output missing tasks_to_rerun section" },
|
|
113
113
|
],
|
|
@@ -119,9 +119,9 @@ const OUTPUT_CONTRACTS: OutputContract[] = [
|
|
|
119
119
|
required: [
|
|
120
120
|
...COMMON_OUTPUT_REQUIREMENTS,
|
|
121
121
|
{ name: "verdict", pattern: "<verdict>\\s*(PASS|FAIL|WARN)\\s*</verdict>|\\b(PASS|FAIL|WARN)\\b", message: "Oracle merge output missing PASS/FAIL/WARN verdict" },
|
|
122
|
-
{ name: "confidence", pattern: "<confidence>|\\bconfidence\\b
|
|
122
|
+
{ name: "confidence", pattern: "<confidence>|\\bconfidence\\b", message: "Oracle merge output missing confidence" },
|
|
123
123
|
{ name: "no_ship", pattern: "<no_ship>|no_ship|no ship", message: "Oracle merge output missing no_ship decision" },
|
|
124
|
-
{ name: "blocking_issues", pattern: "<blocking_issues>|blocking issues?|blockers
|
|
124
|
+
{ name: "blocking_issues", pattern: "<blocking_issues>|blocking issues?|blockers?", message: "Oracle merge output missing blocking issues" },
|
|
125
125
|
{ name: "merged_lanes", pattern: "<merged_lanes>|merged_lanes|merged lanes", message: "Oracle merge output missing merged_lanes" },
|
|
126
126
|
],
|
|
127
127
|
},
|
|
@@ -146,8 +146,8 @@ const OUTPUT_CONTRACTS: OutputContract[] = [
|
|
|
146
146
|
required: [
|
|
147
147
|
...COMMON_OUTPUT_REQUIREMENTS,
|
|
148
148
|
{ name: "sources", pattern: "sources?|sources_consulted|URLs?|paths?", message: "Research output missing sources" },
|
|
149
|
-
{ name: "unknowns", pattern: "unknowns?|uncertainties
|
|
150
|
-
{ name: "recommendation", pattern: "recommendation
|
|
149
|
+
{ name: "unknowns", pattern: "unknowns?|uncertainties", message: "Research output missing unknowns/uncertainties" },
|
|
150
|
+
{ name: "recommendation", pattern: "recommendation", message: "Research output missing recommendation" },
|
|
151
151
|
],
|
|
152
152
|
},
|
|
153
153
|
{
|
|
@@ -163,7 +163,7 @@ const OUTPUT_CONTRACTS: OutputContract[] = [
|
|
|
163
163
|
{ name: "facts_or_patterns", pattern: "facts_or_patterns|facts|patterns", message: "Brain lookup output missing facts_or_patterns" },
|
|
164
164
|
{ name: "gaps", pattern: "gaps|source gaps", message: "Brain lookup output missing gaps" },
|
|
165
165
|
{ name: "freshness", pattern: "freshness|stale", message: "Brain lookup output missing freshness" },
|
|
166
|
-
{ name: "confidence", pattern: "confidence
|
|
166
|
+
{ name: "confidence", pattern: "confidence", message: "Brain lookup output missing confidence" },
|
|
167
167
|
{ name: "no_body_storage", pattern: "no_body_storage|bodyStored\s*[:=]\s*false|no body storage", message: "Brain lookup output missing no_body_storage" },
|
|
168
168
|
],
|
|
169
169
|
},
|
|
@@ -218,15 +218,15 @@ const OUTPUT_CONTRACTS: OutputContract[] = [
|
|
|
218
218
|
agentNames: ["specifier", "spec"],
|
|
219
219
|
required: [
|
|
220
220
|
...COMMON_OUTPUT_REQUIREMENTS,
|
|
221
|
-
{ name: "problem", pattern: "<problem>|\\bproblem\\b
|
|
222
|
-
{ name: "context", pattern: "<context>|\\bcontext\\b
|
|
223
|
-
{ name: "objectives", pattern: "<objectives>|objectives
|
|
224
|
-
{ name: "non_goals", pattern: "<non_goals>|non[-_ ]goals
|
|
225
|
-
{ name: "in_scope", pattern: "<in_scope>|in[-_ ]scope
|
|
226
|
-
{ name: "out_of_scope", pattern: "<out_of_scope>|out[-_ ]of[-_ ]scope
|
|
227
|
-
{ name: "constraints", pattern: "<constraints>|constraints
|
|
228
|
-
{ name: "acceptance_criteria", pattern: "<acceptance_criteria>|acceptance criteria|acceptance_criteria
|
|
229
|
-
{ name: "open_questions", pattern: "<open_questions>|open questions|open_questions
|
|
221
|
+
{ name: "problem", pattern: "<problem>|\\bproblem\\b", message: "Spec output missing problem" },
|
|
222
|
+
{ name: "context", pattern: "<context>|\\bcontext\\b", message: "Spec output missing context" },
|
|
223
|
+
{ name: "objectives", pattern: "<objectives>|objectives?", message: "Spec output missing objectives" },
|
|
224
|
+
{ name: "non_goals", pattern: "<non_goals>|non[-_ ]goals?", message: "Spec output missing non_goals" },
|
|
225
|
+
{ name: "in_scope", pattern: "<in_scope>|in[-_ ]scope", message: "Spec output missing in_scope" },
|
|
226
|
+
{ name: "out_of_scope", pattern: "<out_of_scope>|out[-_ ]of[-_ ]scope", message: "Spec output missing out_of_scope" },
|
|
227
|
+
{ name: "constraints", pattern: "<constraints>|constraints?", message: "Spec output missing constraints" },
|
|
228
|
+
{ name: "acceptance_criteria", pattern: "<acceptance_criteria>|acceptance criteria|acceptance_criteria", message: "Spec output missing acceptance_criteria" },
|
|
229
|
+
{ name: "open_questions", pattern: "<open_questions>|open questions|open_questions", message: "Spec output missing open_questions" },
|
|
230
230
|
{ name: "handoff_to_planner", pattern: "<handoff_to_planner>|handoff to planner|handoff_to_planner|planner", message: "Spec output missing handoff_to_planner" },
|
|
231
231
|
],
|
|
232
232
|
},
|
|
@@ -236,15 +236,15 @@ const OUTPUT_CONTRACTS: OutputContract[] = [
|
|
|
236
236
|
agentNames: ["clarifier"],
|
|
237
237
|
required: [
|
|
238
238
|
...COMMON_OUTPUT_REQUIREMENTS,
|
|
239
|
-
{ name: "clarity_score", pattern: "<clarity_score>|clarity_score|clarity score
|
|
239
|
+
{ name: "clarity_score", pattern: "<clarity_score>|clarity_score|clarity score", message: "Clarification output missing clarity_score" },
|
|
240
240
|
{ name: "verdict", pattern: "<verdict>\\s*(CLEAR|NEEDS_CLARIFICATION|BLOCKED)\\s*</verdict>|\\b(CLEAR|NEEDS_CLARIFICATION|BLOCKED)\\b", message: "Clarification output missing CLEAR/NEEDS_CLARIFICATION/BLOCKED verdict" },
|
|
241
241
|
{ name: "allow_plan", pattern: "<allow_plan>\\s*(yes|no)\\s*</allow_plan>|allow_plan\\s*[:=]\\s*(yes|no)", message: "Clarification output missing allow_plan yes/no" },
|
|
242
|
-
{ name: "ambiguities", pattern: "<ambiguities>|ambiguities
|
|
242
|
+
{ name: "ambiguities", pattern: "<ambiguities>|ambiguities", message: "Clarification output missing ambiguities" },
|
|
243
243
|
{ name: "questions", pattern: "<questions>|questions?", message: "Clarification output missing questions" },
|
|
244
|
-
{ name: "assumptions", pattern: "<assumptions>|assumptions
|
|
245
|
-
{ name: "refined_spec", pattern: "<refined_spec>|refined spec|refined_spec
|
|
244
|
+
{ name: "assumptions", pattern: "<assumptions>|assumptions?", message: "Clarification output missing assumptions" },
|
|
245
|
+
{ name: "refined_spec", pattern: "<refined_spec>|refined spec|refined_spec", message: "Clarification output missing refined_spec" },
|
|
246
246
|
{ name: "minimum_to_plan", pattern: "<minimum_to_plan>|minimum to plan|minimum_to_plan", message: "Clarification output missing minimum_to_plan" },
|
|
247
|
-
{ name: "acceptance_criteria", pattern: "<acceptance_criteria>|acceptance criteria|acceptance_criteria
|
|
247
|
+
{ name: "acceptance_criteria", pattern: "<acceptance_criteria>|acceptance criteria|acceptance_criteria", message: "Clarification output missing acceptance_criteria" }
|
|
248
248
|
],
|
|
249
249
|
},
|
|
250
250
|
{
|
|
@@ -5,7 +5,7 @@ function detectCanonicalPatterns(text: string): string[] {
|
|
|
5
5
|
if (/TASK\s*:|EXPECTED\s+OUTCOME\s*:|MUST\s+NOT/i.test(text)) patterns.add("delegation.contract.structured");
|
|
6
6
|
if (/delegate_(agent|task)|sub-?agent|oracle|explore/i.test(text)) patterns.add("routing.subagent.specialized");
|
|
7
7
|
if (/PASS|FAIL|WARN|verdict/i.test(text)) patterns.add("verification.verdict_first");
|
|
8
|
-
if (/evidence|
|
|
8
|
+
if (/evidence|proof|sentinel|DONE/i.test(text)) patterns.add("verification.evidence_required");
|
|
9
9
|
if (/factory_run|software factory|factory|manifest|checkpoint/i.test(text)) patterns.add("factory.workflow.manifest_checkpoint");
|
|
10
10
|
if (/damage-control|destructive|secret|zero-access|sandbox/i.test(text)) patterns.add("safety.damage_control");
|
|
11
11
|
if (/truncat|output cut|silent response/i.test(text)) patterns.add("failure.output.truncation");
|
|
@@ -69,6 +69,26 @@ export interface TodoSplitRequest {
|
|
|
69
69
|
hasFinalMarker: boolean;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
export type TodoPeerStatusClaim = "done" | "incomplete" | "blocked";
|
|
73
|
+
|
|
74
|
+
export interface TodoPeerResultItem {
|
|
75
|
+
todoId?: string;
|
|
76
|
+
statusClaim?: TodoPeerStatusClaim;
|
|
77
|
+
evidenceRefs: string[];
|
|
78
|
+
validationCommands: string[];
|
|
79
|
+
risks: string[];
|
|
80
|
+
acceptanceBlockers: string[];
|
|
81
|
+
noShip?: boolean;
|
|
82
|
+
hasFinalMarker: boolean;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface TodoPeerResultParseResult {
|
|
86
|
+
contract?: "TODO_PEER_RESULT.v1" | "TODO_PEER_BUNDLE_RESULT.v1";
|
|
87
|
+
items: TodoPeerResultItem[];
|
|
88
|
+
hasFinalMarker: boolean;
|
|
89
|
+
errors: string[];
|
|
90
|
+
}
|
|
91
|
+
|
|
72
92
|
export interface GoalTodoDelegationRef {
|
|
73
93
|
runId?: string;
|
|
74
94
|
agent?: string;
|
|
@@ -30,6 +30,8 @@ import type {
|
|
|
30
30
|
GoalTodoSummary,
|
|
31
31
|
ResolveGoalTodoAction,
|
|
32
32
|
TodoClaimValidationResult,
|
|
33
|
+
TodoPeerResultItem,
|
|
34
|
+
TodoPeerResultParseResult,
|
|
33
35
|
TodoSplitRequest,
|
|
34
36
|
} from "./goal-todo-types.js";
|
|
35
37
|
import { recordZcommitOwnedPaths, type ZcommitChildChangedPathRef } from "../git/git-ops.js";
|
|
@@ -67,6 +69,8 @@ export type {
|
|
|
67
69
|
GoalTodoSummary,
|
|
68
70
|
ResolveGoalTodoAction,
|
|
69
71
|
TodoClaimValidationResult,
|
|
72
|
+
TodoPeerResultItem,
|
|
73
|
+
TodoPeerResultParseResult,
|
|
70
74
|
TodoSplitRequest,
|
|
71
75
|
TodoSplitRequestAction,
|
|
72
76
|
TodoSplitRiskLevel,
|
|
@@ -395,15 +399,21 @@ function applyEvent(state: GoalTodoState, event: GoalTodoEvent): void {
|
|
|
395
399
|
childChangedPaths: event.childChangedPaths ?? [],
|
|
396
400
|
returnedAt: event.at,
|
|
397
401
|
};
|
|
402
|
+
const claimStatus: GoalTodoStatus = event.statusClaim === "blocked"
|
|
403
|
+
? "blocked"
|
|
404
|
+
: event.statusClaim === "incomplete" || event.noShip === true || (event.acceptanceBlockers ?? []).length > 0 || event.targetReadiness === "needs_parent_review" || event.targetReadiness === "blocked"
|
|
405
|
+
? "needs_review"
|
|
406
|
+
: "claim_returned";
|
|
398
407
|
replaceNode(state, applyPatchToNode(existing, {
|
|
399
|
-
status:
|
|
408
|
+
status: claimStatus,
|
|
409
|
+
owner: claimStatus === "claim_returned" ? existing.owner : "agent",
|
|
400
410
|
evidenceRefs,
|
|
401
411
|
validationCommands,
|
|
402
412
|
delegation: existing.delegation ? { ...existing.delegation, status: "claim_returned" } : undefined,
|
|
403
413
|
claim,
|
|
404
414
|
artifacts: { ...(existing.artifacts ?? {}), outputHash: event.outputHash ?? event.claimHash },
|
|
405
|
-
blocker: event.noShip === true ? "delegated claim returned advisory no_ship=true; parent review required" :
|
|
406
|
-
reviewNoShip:
|
|
415
|
+
blocker: claimStatus === "claim_returned" ? undefined : event.acceptanceBlockers?.[0] ?? (event.noShip === true ? "delegated claim returned advisory no_ship=true; parent review required" : `delegated claim status ${event.statusClaim ?? "needs_review"}; parent review required`),
|
|
416
|
+
reviewNoShip: claimStatus === "claim_returned" ? undefined : true,
|
|
407
417
|
}));
|
|
408
418
|
}
|
|
409
419
|
return;
|
|
@@ -880,7 +890,7 @@ export function splitGoalTodo(pi: ExtensionAPI, state: HarnessRuntimeState, goal
|
|
|
880
890
|
return children;
|
|
881
891
|
}
|
|
882
892
|
|
|
883
|
-
export function linkGoalTodoDelegation(pi: ExtensionAPI, state: HarnessRuntimeState, goalId: string, todoId: string, input: { runId: string; agent?: string; childGoalId?: string; requestId?: string; delegationDepth?: number }, source: GoalTodoEventSource = "delegation"): GoalTodoNode | undefined {
|
|
893
|
+
export function linkGoalTodoDelegation(pi: ExtensionAPI, state: HarnessRuntimeState, goalId: string, todoId: string, input: { runId: string; agent?: string; childGoalId?: string; requestId?: string; delegationDepth?: number; status?: GoalTodoDelegationStatus }, source: GoalTodoEventSource = "delegation"): GoalTodoNode | undefined {
|
|
884
894
|
const existing = state.goalTodos.nodes.find((node) => node.goalId === goalId && node.id === todoId);
|
|
885
895
|
if (!existing) return undefined;
|
|
886
896
|
const delegation: GoalTodoDelegationRef = {
|
|
@@ -889,7 +899,7 @@ export function linkGoalTodoDelegation(pi: ExtensionAPI, state: HarnessRuntimeSt
|
|
|
889
899
|
childGoalId: input.childGoalId,
|
|
890
900
|
requestId: input.requestId,
|
|
891
901
|
delegationDepth: Math.max(0, Math.trunc(input.delegationDepth ?? existing.delegation?.delegationDepth ?? 1)),
|
|
892
|
-
status: "running",
|
|
902
|
+
status: input.status ?? "running",
|
|
893
903
|
};
|
|
894
904
|
appendGoalTodoEvent(pi, state, { version: 1, kind: "delegate_link", source, goalId, todoId, runId: input.runId, delegation, at: unixSeconds() });
|
|
895
905
|
return state.goalTodos.nodes.find((node) => node.goalId === goalId && node.id === todoId);
|
|
@@ -1250,6 +1260,51 @@ function extractLabeledScalar(text: string, label: string): string | undefined {
|
|
|
1250
1260
|
return match?.[1]?.trim();
|
|
1251
1261
|
}
|
|
1252
1262
|
|
|
1263
|
+
function normalizePeerStatusClaim(value: string | undefined): TodoPeerResultItem["statusClaim"] {
|
|
1264
|
+
const normalized = value?.trim().toLowerCase().replace(/[ -]/g, "_");
|
|
1265
|
+
return normalized === "done" || normalized === "incomplete" || normalized === "blocked" ? normalized : undefined;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
function extractTodoPeerResultItemFromText(text: string, hasFinalMarker: boolean): TodoPeerResultItem {
|
|
1269
|
+
const todoIdMatch = text.match(/todo_id\s*[:=]\s*([^\s]+)/i);
|
|
1270
|
+
const statusRaw = extractLabeledScalar(text, "status_claim") ?? extractLabeledScalar(text, "status");
|
|
1271
|
+
const noShipMatch = text.match(/no_ship\s*[:=]\s*(true|yes|false|no)/i);
|
|
1272
|
+
return {
|
|
1273
|
+
todoId: todoIdMatch?.[1]?.trim(),
|
|
1274
|
+
statusClaim: normalizePeerStatusClaim(statusRaw),
|
|
1275
|
+
evidenceRefs: collectLabeledLines(text, /^\s*(?:[-*]\s*)?evidence_refs\s*[:=]/i),
|
|
1276
|
+
validationCommands: collectLabeledLines(text, /^\s*(?:[-*]\s*)?validation_commands\s*[:=]/i),
|
|
1277
|
+
risks: collectLabeledLines(text, /^\s*(?:[-*]\s*)?risks\s*[:=]/i),
|
|
1278
|
+
acceptanceBlockers: collectLabeledLines(text, /^\s*(?:[-*]\s*)?acceptance_blockers\s*[:=]/i),
|
|
1279
|
+
noShip: noShipMatch ? /^(true|yes)$/i.test(noShipMatch[1]) : undefined,
|
|
1280
|
+
hasFinalMarker,
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
export function extractTodoPeerResultFromText(text: string): TodoPeerResultParseResult {
|
|
1285
|
+
const bundle = /TODO_PEER_BUNDLE_RESULT\.v1/i.test(text);
|
|
1286
|
+
const single = /TODO_PEER_RESULT\.v1/i.test(text);
|
|
1287
|
+
const hasBundleMarker = /FINAL_MARKER\s*:\s*TODO_PEER_BUNDLE_RESULT_END|TODO_PEER_BUNDLE_RESULT_END/.test(text);
|
|
1288
|
+
const hasSingleMarker = /FINAL_MARKER\s*:\s*TODO_PEER_RESULT_END|TODO_PEER_RESULT_END/.test(text);
|
|
1289
|
+
const contract = bundle ? "TODO_PEER_BUNDLE_RESULT.v1" : single ? "TODO_PEER_RESULT.v1" : undefined;
|
|
1290
|
+
const hasFinalMarker = bundle ? hasBundleMarker : single ? hasSingleMarker : false;
|
|
1291
|
+
const errors: string[] = [];
|
|
1292
|
+
if (!contract) return { items: [], hasFinalMarker: false, errors };
|
|
1293
|
+
if (!hasFinalMarker) errors.push("missing_final_marker");
|
|
1294
|
+
const itemTexts = bundle
|
|
1295
|
+
? text.split(/(?=^\s*(?:[-*]\s*)?todo_id\s*[:=])/gim).filter((part) => /todo_id\s*[:=]/i.test(part))
|
|
1296
|
+
: [text];
|
|
1297
|
+
const items = itemTexts.map((part) => extractTodoPeerResultItemFromText(part, hasFinalMarker));
|
|
1298
|
+
if (items.length === 0) errors.push("missing_result_items");
|
|
1299
|
+
for (const item of items) {
|
|
1300
|
+
if (!item.todoId) errors.push("missing_todo_id");
|
|
1301
|
+
if (!item.statusClaim) errors.push(`missing_status_claim:${item.todoId ?? "unknown"}`);
|
|
1302
|
+
if (item.statusClaim === "done" && item.evidenceRefs.length === 0 && item.validationCommands.length === 0) errors.push(`missing_evidence_for_done:${item.todoId ?? "unknown"}`);
|
|
1303
|
+
if (item.noShip === true) errors.push(`no_ship_true:${item.todoId ?? "unknown"}`);
|
|
1304
|
+
}
|
|
1305
|
+
return { contract, items, hasFinalMarker, errors: [...new Set(errors)] };
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1253
1308
|
export function extractTodoClaimValidationFromText(text: string): TodoClaimValidationResult {
|
|
1254
1309
|
const todoIdMatch = text.match(/todo_id\s*[:=]\s*([^\s]+)/i);
|
|
1255
1310
|
const claimHashMatch = text.match(/claim_hash\s*[:=]\s*([a-f0-9]{64})/i);
|
|
@@ -5,7 +5,7 @@ import { sha256 } from "../../core/utils/hashing.js";
|
|
|
5
5
|
import { isRecord } from "../../core/utils/records.js";
|
|
6
6
|
import { safeRunId } from "../../core/utils/paths.js";
|
|
7
7
|
|
|
8
|
-
const STOPWORDS = new Set(["the", "and", "for", "with", "how", "does", "this", "that", "
|
|
8
|
+
const STOPWORDS = new Set(["the", "and", "for", "with", "how", "does", "this", "that", "using", "use", "project", "style"]);
|
|
9
9
|
const DEFAULT_SCAN_DIR = "reports/project-dna-scans/project-dna-factory-smoke";
|
|
10
10
|
const SAFE_SCAN_PREFIXES = ["reports/project-dna-scans/"];
|
|
11
11
|
const MAX_CONTEXT_TOKENS_CAP = 8000;
|
|
@@ -89,7 +89,7 @@ export function evaluateBudgetPreflightDryRun(input: BudgetPreflightDryRunInput)
|
|
|
89
89
|
|
|
90
90
|
function outputHasEvidenceMarker(output: string | undefined): boolean {
|
|
91
91
|
if (!output) return false;
|
|
92
|
-
return /(?:<evidence>[\s\S]*?<\/evidence>|<evidence>|\bevidence\b|
|
|
92
|
+
return /(?:<evidence>[\s\S]*?<\/evidence>|<evidence>|\bevidence\b|proof)/i.test(output);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
export function detectOracleFail(output: string | undefined): { oracleFailed: boolean; noShip: boolean; stopCondition: ChildStopCondition } {
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Directory scope
|
|
2
2
|
|
|
3
|
-
-
|
|
4
|
-
-
|
|
3
|
+
- Factory validation, agentic plans, factory execution, and quarantine review/activate/verify.
|
|
4
|
+
- This directory must not register Pi tools directly; runtime delegates here.
|
|
5
5
|
|
|
6
6
|
# Invariants
|
|
7
7
|
|
|
8
|
-
-
|
|
9
|
-
- `plan_only`
|
|
10
|
-
- Pilot
|
|
11
|
-
- Factory-forge quarantine
|
|
12
|
-
- Activation
|
|
13
|
-
-
|
|
8
|
+
- Preserve `SMOKE_PASSED.sentinel`, `PILOT_PASSED.sentinel`, `BATCH_PASSED.sentinel`, and `DONE.sentinel`.
|
|
9
|
+
- `plan_only` does not create completion sentinels.
|
|
10
|
+
- Pilot requires a persisted oracle review and a multi-item manifest.
|
|
11
|
+
- Factory-forge quarantine never self-activates.
|
|
12
|
+
- Activation refuses overwrite and requires the exact phrase.
|
|
13
|
+
- Do not change artifact names, statuses, or validations.
|
|
14
14
|
|
|
15
15
|
# Imports
|
|
16
16
|
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
17
|
+
- May import utils/safety/output-contracts/telemetry/child-runner as needed.
|
|
18
|
+
- Forbidden: importing from `index.ts`.
|
|
19
|
+
- Use runtime-relative imports with a `.js` suffix.
|
|
20
20
|
|
|
21
|
-
#
|
|
21
|
+
# Local validation
|
|
22
22
|
|
|
23
23
|
- `npm run check -- --pretty false`.
|
|
24
|
-
- `npm run smoke:harness`
|
|
24
|
+
- `npm run smoke:harness` after any factory/quarantine slice.
|