@yemi33/minions 0.1.2370 → 0.1.2372
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/bin/minions.js +38 -11
- package/dashboard/js/render-other.js +2 -30
- package/dashboard/js/render-work-items.js +0 -34
- package/dashboard/js/settings.js +12 -27
- package/dashboard/pages/tools.html +1 -1
- package/dashboard.js +80 -257
- package/docs/README.md +2 -3
- package/docs/architecture.excalidraw +2 -2
- package/docs/auto-discovery.md +3 -3
- package/docs/completion-reports.md +0 -121
- package/docs/copilot-cli-schema.md +9 -17
- package/docs/deprecated.json +0 -51
- package/docs/design-state-storage.md +4 -4
- package/docs/harness-propagation.md +33 -263
- package/docs/human-vs-automated.md +1 -1
- package/docs/named-agents.md +0 -2
- package/docs/plan-lifecycle.md +5 -6
- package/docs/qa-runbook-lifecycle.md +10 -25
- package/docs/shared-lifecycle-module-map.md +2 -10
- package/engine/ado-comment.js +5 -11
- package/engine/ado.js +10 -5
- package/engine/cleanup.js +16 -36
- package/engine/cli.js +13 -175
- package/engine/comment-format.js +8 -182
- package/engine/db/index.js +60 -10
- package/engine/db/migrations/018-sql-only-cutover.js +56 -0
- package/engine/dispatch-store.js +0 -114
- package/engine/dispatch.js +1 -6
- package/engine/gh-comment.js +2 -10
- package/engine/github.js +8 -6
- package/engine/lifecycle.js +58 -173
- package/engine/llm.js +9 -11
- package/engine/managed-spawn.js +1 -6
- package/engine/memory-store.js +8 -6
- package/engine/metrics-store.js +4 -128
- package/engine/pipeline.js +4 -7
- package/engine/playbook.js +8 -196
- package/engine/prd-store.js +115 -141
- package/engine/preflight.js +8 -55
- package/engine/projects.js +34 -41
- package/engine/pull-requests-store.js +9 -234
- package/engine/qa-runs.js +4 -15
- package/engine/qa-sessions.js +5 -17
- package/engine/queries.js +23 -103
- package/engine/routing.js +1 -1
- package/engine/runtimes/claude.js +1 -2
- package/engine/runtimes/codex.js +0 -2
- package/engine/runtimes/copilot.js +1 -4
- package/engine/shared-branch-pr-reconcile.js +2 -5
- package/engine/shared.js +174 -533
- package/engine/small-state-store.js +12 -647
- package/engine/spawn-agent.js +5 -96
- package/engine/state-operations.js +166 -0
- package/engine/supervisor.js +0 -1
- package/engine/watch-actions.js +2 -3
- package/engine/watches-store.js +2 -128
- package/engine/work-items-store.js +3 -254
- package/engine.js +102 -334
- package/package.json +2 -2
- package/playbooks/build-fix-complex.md +0 -4
- package/playbooks/fix.md +0 -4
- package/playbooks/implement-shared.md +0 -4
- package/playbooks/implement.md +0 -4
- package/playbooks/plan-to-prd.md +16 -11
- package/playbooks/plan.md +0 -4
- package/playbooks/review.md +0 -6
- package/playbooks/shared-rules.md +0 -65
- package/docs/harness-transparency.md +0 -199
- package/docs/project-skills.md +0 -193
- package/engine/discover-project-skills.js +0 -673
- package/engine/playbook-intents.js +0 -76
package/dashboard.js
CHANGED
|
@@ -82,24 +82,6 @@ const IS_DEV_MODE = fs.existsSync(path.join(MINIONS_DIR, '.git'));
|
|
|
82
82
|
const FAVICON_EMOJI = IS_DEV_MODE ? '🚧' : '👽';
|
|
83
83
|
const TITLE_SUFFIX = IS_DEV_MODE ? ' [DEV]' : '';
|
|
84
84
|
|
|
85
|
-
// Startup size guard (#1167): fail fast with a clear error when dispatch.json /
|
|
86
|
-
// cooldowns.json have ballooned past ENGINE_DEFAULTS.maxStateFileBytes. Without
|
|
87
|
-
// this, V8 silently OOMs on JSON.parse(~1 GB) and the operator has no hint as to
|
|
88
|
-
// which file is bloated. The thrown error names the file and directs to
|
|
89
|
-
// engine/contexts/ where sidecars live.
|
|
90
|
-
(() => {
|
|
91
|
-
const stateFiles = [
|
|
92
|
-
DISPATCH_PATH,
|
|
93
|
-
path.join(ENGINE_DIR, 'cooldowns.json'),
|
|
94
|
-
];
|
|
95
|
-
for (const fp of stateFiles) {
|
|
96
|
-
try { shared.assertStateFileSize(fp); } catch (e) {
|
|
97
|
-
console.error('\n[dashboard] STARTUP ABORTED — ' + e.message + '\n');
|
|
98
|
-
process.exit(78); // 78 = configuration error; consistent with spawn-agent.js
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
})();
|
|
102
|
-
|
|
103
85
|
let CONFIG = queries.getConfig();
|
|
104
86
|
let PROJECTS = _getProjects(CONFIG);
|
|
105
87
|
// PORT resolution chain (W-mq5nwl9l): explicit CLI arg (argv[2]) > env
|
|
@@ -469,6 +451,18 @@ function mergeSettingsConfigUpdate(current, candidate, body, patch = {}) {
|
|
|
469
451
|
// `worktreeMode`. Drop any stale legacy key on every settings save so a
|
|
470
452
|
// migrated project never carries both fields.
|
|
471
453
|
delete currentProject.worktreeMode;
|
|
454
|
+
// W-mrchrfe100067d3a — mirror the per-project additionalRepoScopes
|
|
455
|
+
// allowlist the same way: present on candidate → copy; absent → clear so
|
|
456
|
+
// the endpoint falls back to single-scope matching. Without this branch
|
|
457
|
+
// the validated update (set/deleted on `candidate` in
|
|
458
|
+
// handleSettingsUpdate) is silently dropped before reaching disk — the
|
|
459
|
+
// endpoint would return 200 but persist nothing (issue: fix before
|
|
460
|
+
// configuring minions-opg to poll the secondary yemi33/minions scope).
|
|
461
|
+
if (Object.prototype.hasOwnProperty.call(candidateProject, 'additionalRepoScopes')) {
|
|
462
|
+
currentProject.additionalRepoScopes = candidateProject.additionalRepoScopes;
|
|
463
|
+
} else {
|
|
464
|
+
delete currentProject.additionalRepoScopes;
|
|
465
|
+
}
|
|
472
466
|
}
|
|
473
467
|
}
|
|
474
468
|
shared.pruneDefaultClaudeConfig(current);
|
|
@@ -579,6 +573,9 @@ function normalizePrMetadata(metadata) {
|
|
|
579
573
|
description: typeof metadata.description === 'string' ? metadata.description : '',
|
|
580
574
|
branch: typeof metadata.branch === 'string' ? metadata.branch.trim().replace(/^refs\/heads\//i, '') : '',
|
|
581
575
|
author: typeof metadata.author === 'string' ? metadata.author.trim() : '',
|
|
576
|
+
// W-mrezh0yb0007f733 — carry the platform's real creation timestamp through
|
|
577
|
+
// so the manual-link path can seed `created` with it instead of now().
|
|
578
|
+
created: typeof metadata.created === 'string' ? metadata.created.trim() : '',
|
|
582
579
|
};
|
|
583
580
|
}
|
|
584
581
|
|
|
@@ -1365,7 +1362,12 @@ function linkPullRequestForTracking({ url, title, project: projectName, contextO
|
|
|
1365
1362
|
branch: metadata?.branch || '',
|
|
1366
1363
|
reviewStatus: 'pending',
|
|
1367
1364
|
status: 'active',
|
|
1368
|
-
|
|
1365
|
+
// W-mrezh0yb0007f733 — prefer the platform's real creationDate (fetched into
|
|
1366
|
+
// metadata for ADO before linking) over link-time now(). When it isn't
|
|
1367
|
+
// available yet (e.g. GitHub, where enrichment runs async after this insert,
|
|
1368
|
+
// or an ADO metadata-fetch failure), fall back to now() — the next PR poll
|
|
1369
|
+
// corrects it from the platform via shared.shouldAdoptPlatformCreated.
|
|
1370
|
+
created: metadata?.created || new Date().toISOString(),
|
|
1369
1371
|
url,
|
|
1370
1372
|
prdItems: linkedWorkItemId ? [linkedWorkItemId] : [],
|
|
1371
1373
|
contextOnly: resolvedContextOnly,
|
|
@@ -1499,161 +1501,10 @@ function _resolveSkillReadPath({ file, dir, source, config, skillFiles } = {}) {
|
|
|
1499
1501
|
return null;
|
|
1500
1502
|
}
|
|
1501
1503
|
|
|
1502
|
-
// ──
|
|
1503
|
-
//
|
|
1504
|
-
//
|
|
1505
|
-
//
|
|
1506
|
-
// and the canonical footgun: per-project assets that live as uncommitted files
|
|
1507
|
-
// in the operator's main checkout but won't propagate to fresh worktrees.
|
|
1508
|
-
//
|
|
1509
|
-
// Surfaced via GET /api/harness/diagnostics and rendered on the Tools page so
|
|
1510
|
-
// operators can spot a missing or unpropagated harness asset without leaving
|
|
1511
|
-
// the dashboard. See docs/harness-propagation.md for the full contract.
|
|
1512
|
-
const HARNESS_FOOTGUN_DIRS = [
|
|
1513
|
-
'.claude/skills',
|
|
1514
|
-
'.claude/commands',
|
|
1515
|
-
'.copilot/skills',
|
|
1516
|
-
'.copilot/commands',
|
|
1517
|
-
'.agents/skills',
|
|
1518
|
-
'.agents/commands',
|
|
1519
|
-
];
|
|
1520
|
-
const HARNESS_FOOTGUN_FILES = [
|
|
1521
|
-
'.claude/.mcp.json',
|
|
1522
|
-
'.copilot/.mcp.json',
|
|
1523
|
-
'.mcp.json',
|
|
1524
|
-
];
|
|
1525
|
-
|
|
1526
|
-
const _PROJECT_LOCAL_FOOTGUN_WARNING =
|
|
1527
|
-
'These files exist in your main checkout but are uncommitted or untracked in ' +
|
|
1528
|
-
'git. A fresh `git worktree add` for a mutating dispatch will NOT see them, so ' +
|
|
1529
|
-
'the dispatched agent silently underperforms. Fix: commit them, move them to ' +
|
|
1530
|
-
'user scope (~/.claude/skills/..., ~/.copilot/skills/...), or flip the project ' +
|
|
1531
|
-
'to live-checkout mode (checkoutMode: live).';
|
|
1532
|
-
|
|
1533
|
-
function _walkUncommittedHarnessAssets(absStart, rootDir, projectName) {
|
|
1534
|
-
const results = [];
|
|
1535
|
-
const stack = [absStart];
|
|
1536
|
-
let count = 0;
|
|
1537
|
-
while (stack.length > 0 && count < 500) {
|
|
1538
|
-
const cur = stack.pop();
|
|
1539
|
-
let entries;
|
|
1540
|
-
try { entries = fs.readdirSync(cur, { withFileTypes: true }); }
|
|
1541
|
-
catch { continue; }
|
|
1542
|
-
for (const ent of entries) {
|
|
1543
|
-
if (ent.name === '.git' || ent.name === 'node_modules') continue;
|
|
1544
|
-
const full = path.join(cur, ent.name);
|
|
1545
|
-
if (ent.isDirectory()) { stack.push(full); continue; }
|
|
1546
|
-
if (!ent.isFile()) continue;
|
|
1547
|
-
const rel = path.relative(rootDir, full);
|
|
1548
|
-
const normalized = rel.replace(/\\/g, '/');
|
|
1549
|
-
let kind = null;
|
|
1550
|
-
if (
|
|
1551
|
-
normalized.startsWith('.claude/skills/') ||
|
|
1552
|
-
normalized.startsWith('.copilot/skills/') ||
|
|
1553
|
-
normalized.startsWith('.agents/skills/')
|
|
1554
|
-
) kind = 'skill';
|
|
1555
|
-
else if (
|
|
1556
|
-
normalized.startsWith('.claude/commands/') ||
|
|
1557
|
-
normalized.startsWith('.copilot/commands/') ||
|
|
1558
|
-
normalized.startsWith('.agents/commands/')
|
|
1559
|
-
) kind = 'command';
|
|
1560
|
-
else if (
|
|
1561
|
-
normalized === '.mcp.json' ||
|
|
1562
|
-
normalized === '.claude/.mcp.json' ||
|
|
1563
|
-
normalized === '.copilot/.mcp.json'
|
|
1564
|
-
) kind = 'mcp';
|
|
1565
|
-
if (!kind) continue;
|
|
1566
|
-
results.push({ file: rel, kind, scope: `project:${projectName}` });
|
|
1567
|
-
count++;
|
|
1568
|
-
if (count >= 500) break;
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
return results;
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
|
-
function _scanProjectLocalHarnessFootgun(project) {
|
|
1575
|
-
const out = {
|
|
1576
|
-
project: project && project.name ? project.name : '',
|
|
1577
|
-
localPath: project && project.localPath ? project.localPath : '',
|
|
1578
|
-
uncommittedAssets: [],
|
|
1579
|
-
footgunWarning: _PROJECT_LOCAL_FOOTGUN_WARNING,
|
|
1580
|
-
};
|
|
1581
|
-
if (!project || !project.localPath) return out;
|
|
1582
|
-
// Skip the synchronous `git status` for UNC / WSL-from-Windows paths
|
|
1583
|
-
// (\\wsl.localhost\..., \\wsl$\..., or any \\server\share). git/stat over a
|
|
1584
|
-
// WSL UNC mount can hang for minutes; even capped at the 10s timeout below it
|
|
1585
|
-
// hard-blocks the dashboard event loop on every poll, freezing the whole
|
|
1586
|
-
// dashboard. Such projects can't carry uncommitted project-local harness
|
|
1587
|
-
// assets the engine would propagate anyway, so returning the empty shell here
|
|
1588
|
-
// is correct, not lossy. (Load-reduction audit 2026-06-24.)
|
|
1589
|
-
const _lp = String(project.localPath);
|
|
1590
|
-
if (_lp.startsWith('\\\\') || _lp.startsWith('//')) return out;
|
|
1591
|
-
let raw = '';
|
|
1592
|
-
try {
|
|
1593
|
-
const { execFileSync } = require('child_process');
|
|
1594
|
-
raw = execFileSync('git', ['status', '--porcelain', '-z'], {
|
|
1595
|
-
cwd: project.localPath,
|
|
1596
|
-
encoding: 'utf8',
|
|
1597
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
1598
|
-
timeout: 10000,
|
|
1599
|
-
// W-mqecdoot — MUST set windowsHide. This runs per-project on every
|
|
1600
|
-
// GET /api/harness/diagnostics hit (Tools/Harness page, polled), and a
|
|
1601
|
-
// detached dashboard has no console — so without this each call pops a
|
|
1602
|
-
// visible git.exe console window per project ("infinite git windows").
|
|
1603
|
-
// This was the only git spawn in the codebase missing windowsHide.
|
|
1604
|
-
windowsHide: true,
|
|
1605
|
-
});
|
|
1606
|
-
} catch { return out; }
|
|
1607
|
-
if (!raw) return out;
|
|
1608
|
-
const records = raw.split('\0').filter(Boolean);
|
|
1609
|
-
const seen = new Set();
|
|
1610
|
-
for (const rec of records) {
|
|
1611
|
-
if (rec.length < 4) continue;
|
|
1612
|
-
const filePath = rec.slice(3);
|
|
1613
|
-
const normalized = filePath.replace(/\\/g, '/');
|
|
1614
|
-
const isDirRecord = normalized.endsWith('/');
|
|
1615
|
-
const trimmed = isDirRecord ? normalized.slice(0, -1) : normalized;
|
|
1616
|
-
let overlaps = false;
|
|
1617
|
-
for (const dir of HARNESS_FOOTGUN_DIRS) {
|
|
1618
|
-
if (
|
|
1619
|
-
trimmed === dir ||
|
|
1620
|
-
trimmed.startsWith(dir + '/') ||
|
|
1621
|
-
dir.startsWith(trimmed + '/')
|
|
1622
|
-
) { overlaps = true; break; }
|
|
1623
|
-
}
|
|
1624
|
-
if (!overlaps) {
|
|
1625
|
-
for (const file of HARNESS_FOOTGUN_FILES) {
|
|
1626
|
-
if (trimmed === file || file.startsWith(trimmed + '/')) { overlaps = true; break; }
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1629
|
-
if (!overlaps) continue;
|
|
1630
|
-
|
|
1631
|
-
const absStart = path.join(project.localPath, filePath);
|
|
1632
|
-
let stat;
|
|
1633
|
-
try { stat = fs.statSync(absStart); } catch { continue; }
|
|
1634
|
-
if (stat.isDirectory()) {
|
|
1635
|
-
for (const asset of _walkUncommittedHarnessAssets(absStart, project.localPath, out.project)) {
|
|
1636
|
-
if (seen.has(asset.file)) continue;
|
|
1637
|
-
seen.add(asset.file);
|
|
1638
|
-
out.uncommittedAssets.push(asset);
|
|
1639
|
-
}
|
|
1640
|
-
} else {
|
|
1641
|
-
let kind = 'other';
|
|
1642
|
-
if (normalized.includes('/skills/')) kind = 'skill';
|
|
1643
|
-
else if (normalized.includes('/commands/')) kind = 'command';
|
|
1644
|
-
else if (normalized.endsWith('.mcp.json')) kind = 'mcp';
|
|
1645
|
-
if (seen.has(filePath)) continue;
|
|
1646
|
-
seen.add(filePath);
|
|
1647
|
-
out.uncommittedAssets.push({ file: filePath, kind, scope: `project:${out.project}` });
|
|
1648
|
-
}
|
|
1649
|
-
}
|
|
1650
|
-
return out;
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
// TTL cache for the harness diagnostic. The build runs a synchronous `git
|
|
1654
|
-
// status` + directory walks per project; the Tools page polls it while open,
|
|
1655
|
-
// so without a cache it re-runs the whole sweep every 4s and blocks the event
|
|
1656
|
-
// loop each time. The diagnostic is rare-change, so a short TTL is ample.
|
|
1504
|
+
// ── Runtime harness inventory ─────────────────────────────────────────────────
|
|
1505
|
+
// Read-only view of native skill, command, and MCP locations declared by each
|
|
1506
|
+
// adapter. It does not influence spawning or attest what a runtime loaded.
|
|
1507
|
+
// The inventory is rare-change, so a short TTL is ample.
|
|
1657
1508
|
// Bypassed whenever explicit opts are supplied (unit tests pass homeDir/
|
|
1658
1509
|
// projects/engineConfig) so test determinism is unaffected.
|
|
1659
1510
|
// (Load-reduction audit 2026-06-24.)
|
|
@@ -1730,29 +1581,7 @@ function _buildHarnessDiagnosticsUncached(opts = {}) {
|
|
|
1730
1581
|
collectMissing(mcpConfigPaths, runtimeName, 'mcp');
|
|
1731
1582
|
}
|
|
1732
1583
|
|
|
1733
|
-
let addDirSnapshot = [];
|
|
1734
|
-
if (preflight && registry) {
|
|
1735
|
-
let fleetRuntime = null;
|
|
1736
|
-
try { fleetRuntime = registry.resolveRuntime(fleetDefaultCli); }
|
|
1737
|
-
catch { fleetRuntime = null; }
|
|
1738
|
-
if (fleetRuntime) {
|
|
1739
|
-
const snapshot = preflight._computeAddDirSnapshot(fleetRuntime, {
|
|
1740
|
-
minionsHome: MINIONS_DIR,
|
|
1741
|
-
homeDir,
|
|
1742
|
-
existsFn,
|
|
1743
|
-
});
|
|
1744
|
-
if (Array.isArray(snapshot)) addDirSnapshot = snapshot.map(slimRow);
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
|
|
1748
1584
|
const suppressed = [];
|
|
1749
|
-
if (engineConfig.copilotSuppressAgentsMd !== false) {
|
|
1750
|
-
suppressed.push({
|
|
1751
|
-
flag: 'copilotSuppressAgentsMd',
|
|
1752
|
-
value: true,
|
|
1753
|
-
effect: 'Copilot --no-custom-instructions: in-tree AGENTS.md is NOT auto-loaded, so Minions playbook prompts are not overridden by repo-local instructions.',
|
|
1754
|
-
});
|
|
1755
|
-
}
|
|
1756
1585
|
if (engineConfig.copilotDisableBuiltinMcps !== false) {
|
|
1757
1586
|
suppressed.push({
|
|
1758
1587
|
flag: 'copilotDisableBuiltinMcps',
|
|
@@ -1760,22 +1589,10 @@ function _buildHarnessDiagnosticsUncached(opts = {}) {
|
|
|
1760
1589
|
effect: 'Copilot --disable-builtin-mcps: bundled github-mcp-server is stripped so dispatched agents do not open a parallel PR alongside the Minions PR.',
|
|
1761
1590
|
});
|
|
1762
1591
|
}
|
|
1763
|
-
if (engineConfig.hermeticHarness === true) {
|
|
1764
|
-
suppressed.push({
|
|
1765
|
-
flag: 'hermeticHarness',
|
|
1766
|
-
value: true,
|
|
1767
|
-
effect: 'Hermetic harness mode: only the engine playbook + system prompt are surfaced; user-scope skills, commands, and MCP configs are NOT propagated to this dispatch.',
|
|
1768
|
-
});
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
|
-
const projectLocalOnMain = projects.map(_scanProjectLocalHarnessFootgun);
|
|
1772
|
-
|
|
1773
1592
|
return {
|
|
1774
1593
|
fleetDefaultCli,
|
|
1775
1594
|
runtimes,
|
|
1776
|
-
addDirSnapshot,
|
|
1777
1595
|
suppressed,
|
|
1778
|
-
projectLocalOnMain,
|
|
1779
1596
|
missingDirs,
|
|
1780
1597
|
};
|
|
1781
1598
|
}
|
|
@@ -1841,6 +1658,11 @@ let _plansCacheTs = 0;
|
|
|
1841
1658
|
const PLANS_CACHE_TTL_MS = 5000;
|
|
1842
1659
|
function invalidatePlansCache() { _plansCache = null; _plansCacheTs = 0; }
|
|
1843
1660
|
|
|
1661
|
+
function statePathExists(filePath) {
|
|
1662
|
+
if (prdStore.parsePrdPath(filePath)) return prdStore.readPrd(filePath) != null;
|
|
1663
|
+
return fs.existsSync(filePath);
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1844
1666
|
// Resolve a plan/PRD file path: .json files live in prd/, .md files in plans/
|
|
1845
1667
|
// Validates that the file stays within the expected directory to prevent path traversal.
|
|
1846
1668
|
function resolvePlanPath(file) {
|
|
@@ -1848,11 +1670,12 @@ function resolvePlanPath(file) {
|
|
|
1848
1670
|
// Validate against both prd/ and prd/archive/
|
|
1849
1671
|
shared.sanitizePath(file, PRD_DIR);
|
|
1850
1672
|
const active = path.join(PRD_DIR, file);
|
|
1851
|
-
if (
|
|
1673
|
+
if (prdStore.readPrd(active)) return active;
|
|
1852
1674
|
const archived = path.join(PRD_DIR, 'archive', file);
|
|
1853
|
-
if (
|
|
1675
|
+
if (prdStore.readPrd(archived)) return archived;
|
|
1854
1676
|
return active;
|
|
1855
1677
|
}
|
|
1678
|
+
|
|
1856
1679
|
// Validate against both plans/ and plans/archive/
|
|
1857
1680
|
shared.sanitizePath(file, PLANS_DIR);
|
|
1858
1681
|
const active = path.join(PLANS_DIR, file);
|
|
@@ -1881,7 +1704,7 @@ function _archivePrdPostProcess({ planFile, archivePath, planPath, plansDir, _mu
|
|
|
1881
1704
|
|
|
1882
1705
|
// (a) Flip the archived FLAG + status + archivedAt. Phase 10 step 4.2b:
|
|
1883
1706
|
// archived-ness is the flag now (not the directory), so set `archived:true`
|
|
1884
|
-
// explicitly. The
|
|
1707
|
+
// explicitly. The path-shaped mutator routes PRDs into SQL.
|
|
1885
1708
|
// When archivePath === planPath (in-place archive) this flags the PRD where it
|
|
1886
1709
|
// sits; when they differ (.md-cascade / legacy move) it flags the moved copy.
|
|
1887
1710
|
try {
|
|
@@ -3245,7 +3068,8 @@ function serveFreshJson(req, res, opts) {
|
|
|
3245
3068
|
return;
|
|
3246
3069
|
}
|
|
3247
3070
|
const mtime = _maxInputMtimeMs(inputs);
|
|
3248
|
-
const
|
|
3071
|
+
const eventVersion = _getCurrentEventVersion();
|
|
3072
|
+
const etag = '"' + tag + '-' + mtime + '-' + eventVersion + '"';
|
|
3249
3073
|
res.setHeader('ETag', etag);
|
|
3250
3074
|
res.setHeader('Cache-Control', 'private, max-age=0, must-revalidate');
|
|
3251
3075
|
if (req && req.headers && req.headers['if-none-match'] === etag) {
|
|
@@ -6254,11 +6078,11 @@ const server = http.createServer(async (req, res) => {
|
|
|
6254
6078
|
const prdDir = path.join(MINIONS_DIR, 'prd');
|
|
6255
6079
|
let prdPath = path.join(prdDir, body.file);
|
|
6256
6080
|
let fromArchive = false;
|
|
6257
|
-
if (!
|
|
6081
|
+
if (!statePathExists(prdPath)) {
|
|
6258
6082
|
prdPath = path.join(prdDir, 'archive', body.file);
|
|
6259
6083
|
fromArchive = true;
|
|
6260
6084
|
}
|
|
6261
|
-
if (!
|
|
6085
|
+
if (!statePathExists(prdPath)) return jsonReply(res, 404, { error: 'PRD not found' });
|
|
6262
6086
|
|
|
6263
6087
|
// If archived, temporarily restore to active so checkPlanCompletion can find it
|
|
6264
6088
|
const activePath = path.join(prdDir, body.file);
|
|
@@ -7218,14 +7042,9 @@ const server = http.createServer(async (req, res) => {
|
|
|
7218
7042
|
const manualPrd = buildManualPrdItemPlan(body);
|
|
7219
7043
|
if (manualPrd.error) return jsonReply(res, 400, { error: manualPrd.error });
|
|
7220
7044
|
|
|
7221
|
-
if (!fs.existsSync(PRD_DIR)) fs.mkdirSync(PRD_DIR, { recursive: true });
|
|
7222
|
-
|
|
7223
7045
|
const planFile = 'manual-' + shared.uid() + '.json';
|
|
7224
7046
|
const manualPrdPath = path.join(PRD_DIR, planFile);
|
|
7225
7047
|
safeWrite(manualPrdPath, manualPrd.plan);
|
|
7226
|
-
// Phase 10 step 2 — this create uses safeWrite (not mutateJsonFileLocked),
|
|
7227
|
-
// so mirror to SQL explicitly. Best-effort.
|
|
7228
|
-
prdStore.mirrorPrdToSql(manualPrdPath, manualPrd.plan);
|
|
7229
7048
|
invalidatePlansCache();
|
|
7230
7049
|
return jsonReply(res, 200, { ok: true, id: manualPrd.id, file: planFile });
|
|
7231
7050
|
} catch (e) { return jsonReply(res, 400, { error: e.message }); }
|
|
@@ -7239,7 +7058,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
7239
7058
|
// the file with JSON, destroying source plans (see /api/plans/approve trap).
|
|
7240
7059
|
if (!body.source.endsWith('.json')) return jsonReply(res, 400, { error: 'expected a PRD JSON filename in `source` (got `' + body.source + '`). Pass prd/<plan>.json, not plans/<plan>.md.' });
|
|
7241
7060
|
const planPath = resolvePlanPath(body.source);
|
|
7242
|
-
if (!
|
|
7061
|
+
if (!statePathExists(planPath)) return jsonReply(res, 404, { error: 'plan file not found' });
|
|
7243
7062
|
// Pre-check: verify item exists before taking the lock
|
|
7244
7063
|
const preCheck = safeJsonObj(planPath);
|
|
7245
7064
|
const preItem = (preCheck.missing_features || []).find(f => f.id === body.itemId);
|
|
@@ -7296,7 +7115,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
7296
7115
|
if (!body.source || !body.itemId) return jsonReply(res, 400, { error: 'source and itemId required' });
|
|
7297
7116
|
if (!body.source.endsWith('.json')) return jsonReply(res, 400, { error: 'expected a PRD JSON filename in `source` (got `' + body.source + '`). Pass prd/<plan>.json, not plans/<plan>.md.' });
|
|
7298
7117
|
const planPath = resolvePlanPath(body.source);
|
|
7299
|
-
if (!
|
|
7118
|
+
if (!statePathExists(planPath)) return jsonReply(res, 404, { error: 'plan file not found' });
|
|
7300
7119
|
let removed = false;
|
|
7301
7120
|
mutateJsonFileLocked(planPath, (plan) => {
|
|
7302
7121
|
if (!plan || Array.isArray(plan) || typeof plan !== 'object') plan = { missing_features: [] };
|
|
@@ -8041,9 +7860,9 @@ const server = http.createServer(async (req, res) => {
|
|
|
8041
7860
|
{ dir: PRD_DIR, archived: false },
|
|
8042
7861
|
{ dir: path.join(PRD_DIR, 'archive'), archived: true },
|
|
8043
7862
|
];
|
|
8044
|
-
// Load work items to check for completed
|
|
8045
|
-
//
|
|
8046
|
-
const centralWi =
|
|
7863
|
+
// Load central work items from the SQL-backed store to check for completed
|
|
7864
|
+
// plan-to-prd conversions.
|
|
7865
|
+
const centralWi = require('./engine/work-items-store').readWorkItemsForScope('central');
|
|
8047
7866
|
const completedPrdFiles = new Set(
|
|
8048
7867
|
centralWi.filter(w => w.type === WORK_TYPE.PLAN_TO_PRD && DONE_STATUSES.has(w.status) && w.planFile)
|
|
8049
7868
|
.map(w => w.planFile)
|
|
@@ -8427,8 +8246,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
8427
8246
|
if (!body.file.endsWith('.md')) return jsonReply(res, 400, { error: 'only .md plans can be executed' });
|
|
8428
8247
|
shared.sanitizePath(body.file, PLANS_DIR);
|
|
8429
8248
|
const planPath = path.join(MINIONS_DIR, 'plans', body.file);
|
|
8430
|
-
if (!
|
|
8431
|
-
const planContent =
|
|
8249
|
+
if (!statePathExists(planPath)) return jsonReply(res, 404, { error: 'plan file not found' });
|
|
8250
|
+
const planContent = safeRead(planPath);
|
|
8432
8251
|
const declaredProject = shared.extractPlanDeclaredProject(planContent);
|
|
8433
8252
|
|
|
8434
8253
|
const feedback = (body.feedback || '').toString().trim();
|
|
@@ -8485,7 +8304,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
8485
8304
|
const body = await readBody(req);
|
|
8486
8305
|
if (!body.source) return jsonReply(res, 400, { error: 'source required' });
|
|
8487
8306
|
const planPath = resolvePlanPath(body.source);
|
|
8488
|
-
if (!
|
|
8307
|
+
if (!statePathExists(planPath)) return jsonReply(res, 404, { error: 'plan file not found' });
|
|
8489
8308
|
const plan = safeJsonObj(planPath);
|
|
8490
8309
|
const planItems = plan.missing_features || [];
|
|
8491
8310
|
|
|
@@ -8581,7 +8400,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
8581
8400
|
if (!body.file) return jsonReply(res, 400, { error: 'file required' });
|
|
8582
8401
|
shared.sanitizePath(body.file, body.file.endsWith('.json') ? PRD_DIR : PLANS_DIR);
|
|
8583
8402
|
const planPath = resolvePlanPath(body.file);
|
|
8584
|
-
if (!
|
|
8403
|
+
if (!statePathExists(planPath)) return jsonReply(res, 404, { error: 'plan not found' });
|
|
8585
8404
|
// Read plan content before deleting — needed for worktree cleanup and source_plan
|
|
8586
8405
|
let planObj = null;
|
|
8587
8406
|
let prdSourcePlan = null;
|
|
@@ -8591,13 +8410,13 @@ const server = http.createServer(async (req, res) => {
|
|
|
8591
8410
|
// Clean up worktrees before deleting work items (needs branch info from work items)
|
|
8592
8411
|
try {
|
|
8593
8412
|
const { cleanupPlanWorktrees } = require('./engine/lifecycle');
|
|
8594
|
-
cleanupPlanWorktrees(body.file, planObj || {}, PROJECTS, getConfig());
|
|
8413
|
+
cleanupPlanWorktrees(body.file, planObj || {}, PROJECTS, queries.getConfig());
|
|
8595
8414
|
} catch (e) { console.error('plan worktree cleanup:', e.message); }
|
|
8596
8415
|
safeUnlink(planPath);
|
|
8597
8416
|
// Phase 10 step 2 — a deleted PRD must also leave SQL, or the read-flip
|
|
8598
8417
|
// (step 3) would resurrect it from a stale mirror row. Best-effort; no-op
|
|
8599
8418
|
// for non-PRD paths (parsePrdPath returns null for plan .md deletes).
|
|
8600
|
-
if (body.file.endsWith('.json')) prdStore.
|
|
8419
|
+
if (body.file.endsWith('.json')) prdStore.deletePrd(planPath);
|
|
8601
8420
|
// Neutralize the `.backup` sidecar so `safeJson` auto-restore can't
|
|
8602
8421
|
// RESURRECT the PRD we just deleted (the live file is gone, but a stray
|
|
8603
8422
|
// `prd/<plan>.json.backup` would be auto-restored on the next safeJson read
|
|
@@ -8667,7 +8486,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
8667
8486
|
const isPrd = body.file.endsWith('.json');
|
|
8668
8487
|
shared.sanitizePath(body.file, isPrd ? PRD_DIR : PLANS_DIR);
|
|
8669
8488
|
const planPath = resolvePlanPath(body.file);
|
|
8670
|
-
if (!
|
|
8489
|
+
if (!statePathExists(planPath)) return jsonReply(res, 404, { error: 'plan not found' });
|
|
8671
8490
|
|
|
8672
8491
|
// Phase 10 step 4.2b: a PRD is archived IN PLACE (flag flip via
|
|
8673
8492
|
// _archivePrdPostProcess — the json stays in prd/, no move, so the
|
|
@@ -8710,19 +8529,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
8710
8529
|
// shipped for the PRD branch in W-mqa13ulk0002def5 (PR #3222) — the
|
|
8711
8530
|
// helper's source-plan move is a safe no-op here because the outer
|
|
8712
8531
|
// handler already renamed body.file into plans/archive/.
|
|
8713
|
-
|
|
8714
|
-
try {
|
|
8715
|
-
prdFiles = fs.readdirSync(PRD_DIR).filter(f => f.endsWith('.json'));
|
|
8716
|
-
} catch (e) {
|
|
8717
|
-
// ENOENT on prd/ is expected for projects without a PRD dir yet —
|
|
8718
|
-
// don't surface as a user-visible warning. Other errors (EACCES,
|
|
8719
|
-
// EIO) get logged + warned.
|
|
8720
|
-
if (e.code !== 'ENOENT') {
|
|
8721
|
-
const warning = `Archive could not enumerate ${PRD_DIR}: ${e.message}`;
|
|
8722
|
-
archiveWarnings.push(warning);
|
|
8723
|
-
console.warn(warning);
|
|
8724
|
-
}
|
|
8725
|
-
}
|
|
8532
|
+
const prdFiles = prdStore.listPrdRows().filter(row => !row.archived).map(row => row.filename);
|
|
8726
8533
|
for (const prdFile of prdFiles) {
|
|
8727
8534
|
const prdLivePath = path.join(PRD_DIR, prdFile);
|
|
8728
8535
|
let prd = null;
|
|
@@ -8777,7 +8584,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
8777
8584
|
|
|
8778
8585
|
try {
|
|
8779
8586
|
const { cleanupPlanWorktrees } = require('./engine/lifecycle');
|
|
8780
|
-
cleanupPlanWorktrees(body.file, plan, PROJECTS, getConfig());
|
|
8587
|
+
cleanupPlanWorktrees(body.file, plan, PROJECTS, queries.getConfig());
|
|
8781
8588
|
} catch (e) { console.error('plan worktree cleanup:', e.message); }
|
|
8782
8589
|
|
|
8783
8590
|
invalidateStatusCache();
|
|
@@ -8804,12 +8611,23 @@ const server = http.createServer(async (req, res) => {
|
|
|
8804
8611
|
// (physically in <dir>/archive/) or in-place (in <dir>/ with the archived
|
|
8805
8612
|
// flag set). Restore from whichever it is.
|
|
8806
8613
|
let liveDest;
|
|
8807
|
-
|
|
8614
|
+
// Existence probes only — NEVER used as the write payload. The actual
|
|
8615
|
+
// restore below (prdStore.restorePrdFromArchive) re-reads the archived
|
|
8616
|
+
// row fresh inside its own transaction immediately before writing, so a
|
|
8617
|
+
// concurrent engine write to that row between this probe and the write
|
|
8618
|
+
// can't be clobbered by a stale in-memory snapshot (W-mrenu075000a94c9).
|
|
8619
|
+
const archivedPrdExists = isJson ? prdStore.readPrd(archivePath) != null : false;
|
|
8620
|
+
const activePrdData = isJson ? prdStore.readPrd(livePath) : null;
|
|
8621
|
+
if (isJson && archivedPrdExists) {
|
|
8622
|
+
const restored = prdStore.restorePrdFromArchive(body.file);
|
|
8623
|
+
if (!restored) return jsonReply(res, 404, { error: 'File not found in archive' });
|
|
8624
|
+
liveDest = livePath;
|
|
8625
|
+
} else if (fs.existsSync(archivePath)) {
|
|
8808
8626
|
// DATA-LOSS GUARD: restoring must not clobber a LIVE plan/PRD of the same
|
|
8809
8627
|
// basename (renameSync overwrites). moveFileNoClobber bumps the restored
|
|
8810
8628
|
// name instead so the existing live file survives.
|
|
8811
8629
|
liveDest = shared.moveFileNoClobber(archivePath, targetDir, body.file);
|
|
8812
|
-
} else if (isJson &&
|
|
8630
|
+
} else if (isJson && activePrdData && shared.isPrdArchived(activePrdData)) {
|
|
8813
8631
|
liveDest = livePath; // in-place flag-archived — already in prd/, just clear the flag below
|
|
8814
8632
|
} else {
|
|
8815
8633
|
return jsonReply(res, 404, { error: 'File not found in archive' });
|
|
@@ -13610,10 +13428,10 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
13610
13428
|
},
|
|
13611
13429
|
});
|
|
13612
13430
|
}},
|
|
13613
|
-
{ method: 'GET', path: '/api/schedules', desc: 'Schedule definitions merged with schedule-
|
|
13431
|
+
{ method: 'GET', path: '/api/schedules', desc: 'Schedule definitions merged with SQL schedule-run state (_lastRun/_lastResult/_lastCompletedAt)', handler: (req, res) => {
|
|
13614
13432
|
return serveFreshJson(req, res, {
|
|
13615
13433
|
tag: 'schedules',
|
|
13616
|
-
inputs: [CONFIG_PATH
|
|
13434
|
+
inputs: [CONFIG_PATH],
|
|
13617
13435
|
builder: () => {
|
|
13618
13436
|
// Read config.json directly via queries.getConfig() rather than
|
|
13619
13437
|
// calling reloadConfig() — the latter cascades into
|
|
@@ -13625,7 +13443,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
13625
13443
|
// (Round-2 review finding #3.)
|
|
13626
13444
|
const cfg = queries.getConfig() || {};
|
|
13627
13445
|
const scheds = cfg.schedules || [];
|
|
13628
|
-
const runs =
|
|
13446
|
+
const runs = require('./engine/small-state-store').readScheduleRuns();
|
|
13629
13447
|
return scheds.map(s => {
|
|
13630
13448
|
const runEntry = runs[s.id];
|
|
13631
13449
|
const _lastRun = typeof runEntry === 'string' ? runEntry : (runEntry?.lastRun || runEntry?.lastCompletedAt || null);
|
|
@@ -13642,12 +13460,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
13642
13460
|
},
|
|
13643
13461
|
});
|
|
13644
13462
|
}},
|
|
13645
|
-
{ method: 'GET', path: '/api/pipelines', desc: 'Pipeline definitions merged with last-5
|
|
13463
|
+
{ method: 'GET', path: '/api/pipelines', desc: 'Pipeline definitions merged with last-5 SQL-backed runs', handler: (req, res) => {
|
|
13646
13464
|
return serveFreshJson(req, res, {
|
|
13647
13465
|
tag: 'pipelines',
|
|
13648
13466
|
inputs: [
|
|
13649
13467
|
path.join(MINIONS_DIR, 'pipelines'),
|
|
13650
|
-
path.join(ENGINE_DIR, 'pipeline-runs.json'),
|
|
13651
13468
|
],
|
|
13652
13469
|
builder: () => {
|
|
13653
13470
|
try {
|
|
@@ -13662,14 +13479,14 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
13662
13479
|
{ method: 'GET', path: '/api/qa-runs-summary', desc: 'Sidebar-counter summary {total, sig} for QA runs (sidebar activity-dot signal)', handler: (req, res) => {
|
|
13663
13480
|
return serveFreshJson(req, res, {
|
|
13664
13481
|
tag: 'qa-runs-summary',
|
|
13665
|
-
inputs: [
|
|
13482
|
+
inputs: [],
|
|
13666
13483
|
builder: () => qaRunsMod.summarizeRunsForStatus(),
|
|
13667
13484
|
});
|
|
13668
13485
|
}},
|
|
13669
13486
|
{ method: 'GET', path: '/api/qa-sessions-summary', desc: 'Sidebar-counter summary {active, sig} for non-terminal QA sessions (drives the QA nav badge — issue #717)', handler: (req, res) => {
|
|
13670
13487
|
return serveFreshJson(req, res, {
|
|
13671
13488
|
tag: 'qa-sessions-summary',
|
|
13672
|
-
inputs: [
|
|
13489
|
+
inputs: [],
|
|
13673
13490
|
builder: () => qaSessionsMod.summarizeActiveSessionsForStatus(),
|
|
13674
13491
|
});
|
|
13675
13492
|
}},
|
|
@@ -13956,7 +13773,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
13956
13773
|
if (token) ghOpts.env = { ...process.env, GH_TOKEN: token };
|
|
13957
13774
|
const result = await shared.shellSafeGh(['api', `repos/${shared.validateGhSlug(slug)}/pulls/${String(prNum)}`], ghOpts);
|
|
13958
13775
|
const d = JSON.parse(result);
|
|
13959
|
-
prData = { title: d.title, description: d.body, branch: d.head?.ref, author: d.user?.login };
|
|
13776
|
+
prData = { title: d.title, description: d.body, branch: d.head?.ref, author: d.user?.login, created: d.created_at };
|
|
13960
13777
|
} else if (adoTarget && !initialPrData) {
|
|
13961
13778
|
try {
|
|
13962
13779
|
prData = await ado.fetchAdoPrMetadata(adoTarget.prNum, adoTarget.adoOrg, adoTarget.adoProj, adoTarget.adoRepo);
|
|
@@ -13983,6 +13800,12 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
13983
13800
|
}
|
|
13984
13801
|
}
|
|
13985
13802
|
if (pr.agent === 'human' && prData.author) pr.agent = prData.author;
|
|
13803
|
+
// W-mrezh0yb0007f733 — for GitHub (and ADO metadata-fetch-retry)
|
|
13804
|
+
// links, `created` was seeded with link-time now(); adopt the
|
|
13805
|
+
// platform's real creation timestamp now that enrichment fetched it.
|
|
13806
|
+
if (prData.created && shared.shouldAdoptPlatformCreated(pr.created, prData.created)) {
|
|
13807
|
+
pr.created = prData.created;
|
|
13808
|
+
}
|
|
13986
13809
|
return prs;
|
|
13987
13810
|
}, { defaultValue: [] });
|
|
13988
13811
|
invalidateStatusCache();
|
|
@@ -14663,8 +14486,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
14663
14486
|
// Skills
|
|
14664
14487
|
{ method: 'GET', path: '/api/skill', desc: 'Read a skill file', params: 'file, source?, dir?', handler: handleSkillRead },
|
|
14665
14488
|
|
|
14666
|
-
//
|
|
14667
|
-
{ method: 'GET', path: '/api/harness/diagnostics', desc: '
|
|
14489
|
+
// Runtime harness inventory
|
|
14490
|
+
{ method: 'GET', path: '/api/harness/diagnostics', desc: 'Read-only inventory of runtime-declared harness locations and explicit orchestration policies', handler: handleHarnessDiagnostics },
|
|
14668
14491
|
|
|
14669
14492
|
// Projects
|
|
14670
14493
|
{ method: 'POST', path: '/api/projects/browse', desc: 'Open folder picker dialog, return selected path', handler: handleProjectsBrowse },
|
package/docs/README.md
CHANGED
|
@@ -43,8 +43,8 @@ Architecture, design proposals, and lifecycle references for people working on t
|
|
|
43
43
|
- [deprecated-process.md](deprecated-process.md) — Schema for `docs/deprecated.json` and the weekly `cleanup-deprecated` audit walk that retires entries past their removal signal.
|
|
44
44
|
- [design-state-storage.md](design-state-storage.md) — Design proposal evaluating five database options for replacing Minions' file-based JSON state; recommends `node:sqlite` (accepted; implementation tracked in CHANGELOG.md Phases 0–10).
|
|
45
45
|
- [harness-mode.md](harness-mode.md) — Tri-Agent Harness Mode (`harness_mode: "tri_agent"` on scheduled tasks): Planner → Generator → Evaluator loop that iterates a shared on-disk artifact until a rubric passes or the iteration cap fires.
|
|
46
|
-
- [harness-propagation.md](harness-propagation.md) —
|
|
47
|
-
|
|
46
|
+
- [harness-propagation.md](harness-propagation.md) — The native runtime
|
|
47
|
+
contract for repository instructions, skills, commands, and MCPs.
|
|
48
48
|
- [kb-dedup-duplicate-pair-investigation.md](kb-dedup-duplicate-pair-investigation.md) — Investigation-only pass diffing suspected duplicate KB filename groups (content-hash dedup for agent-authored reviews/build-reports) to confirm true full-body duplication before any fix is proposed.
|
|
49
49
|
- [kb-pr3223-cascade-archiving.md](kb-pr3223-cascade-archiving.md) — Findings from PR #3223: cascade-archiving PRDs when their linked markdown plan is archived, including the per-concern try/catch pattern and `source_plan` basename-matching invariant in `handlePlansArchive` (`dashboard.js`).
|
|
50
50
|
- [kb-pr696-merge-conflict-docs.md](kb-pr696-merge-conflict-docs.md) — Findings from PR #696: resolving a merge conflict in `docs/kb-sweep.md` with stale line-number citations to `engine/kb-sweep.js`.
|
|
@@ -57,7 +57,6 @@ Architecture, design proposals, and lifecycle references for people working on t
|
|
|
57
57
|
- [pr-auto-fix-dispatch.md](pr-auto-fix-dispatch.md) — Short reference table mapping each PR auto-fix / review dispatch site in `engine.js#discoverFromPrs` to its gate flag, plus the `pollingPaused` / `autoFixPaused` master kill-switches and the per-provider polling gates.
|
|
58
58
|
- [pr-comment-followup.md](pr-comment-followup.md) — PR-comment follow-up dispatch contract: fix/review agents may spin off a new WI via `POST /api/work-items` with `meta.pr_followup` instead of broadening the current PR or rebutting the comment.
|
|
59
59
|
- [pr-review-fix-loop.md](pr-review-fix-loop.md) — How the engine moves a PR from creation through review, fix dispatch, and re-review, including stale-status guards.
|
|
60
|
-
- [project-skills.md](project-skills.md) — Project-local skill discovery (`.claude/skills/`, `.claude/commands/`, `CLAUDE.md` / `.github/copilot-instructions.md` slash-command mentions): how dispatched agents see and steer toward purpose-built tooling the project ships, plus the intent-vocabulary contract.
|
|
61
60
|
- [proposals/repo-pool-for-live-checkout.md](proposals/repo-pool-for-live-checkout.md) — Review-only artifact reproducing the approved plan + PRD for a repo pool covering live-checkout projects (multi-enlistment dispatch), so the design has a git-tracked surface for human comment before the dependent implementation work items land as code PRs.
|
|
62
61
|
- [qa-runbook-lifecycle.md](qa-runbook-lifecycle.md) — End-to-end QA runbook lifecycle (W-mpeiwz6k0005bf34): runbook + run-record storage, `POST /api/qa/runbooks/run` dispatch into the `qa-validate` playbook, artifact contract, and how the `/qa` page mirrors managed-spawn observability.
|
|
63
62
|
- [qa-runbooks.md](qa-runbooks.md) — Per-project QA runbook schema, storage layout (`projects/<name>/runbooks/<id>.json`), CRUD endpoints, run-record lifecycle, and the `qa-validate` agent sidecar contract.
|
|
@@ -1551,11 +1551,11 @@
|
|
|
1551
1551
|
"versionNonce": 1820013027,
|
|
1552
1552
|
"fontSize": 12,
|
|
1553
1553
|
"fontFamily": 3,
|
|
1554
|
-
"text": "copilot adapter\n--autopilot --allow-all\n--disable-builtin-mcps
|
|
1554
|
+
"text": "copilot adapter\n--autopilot --allow-all\n--disable-builtin-mcps",
|
|
1555
1555
|
"textAlign": "center",
|
|
1556
1556
|
"verticalAlign": "middle",
|
|
1557
1557
|
"containerId": "NSvcvc9cT4YYzxMebEDz",
|
|
1558
|
-
"originalText": "copilot adapter\n--autopilot --allow-all\n--disable-builtin-mcps
|
|
1558
|
+
"originalText": "copilot adapter\n--autopilot --allow-all\n--disable-builtin-mcps",
|
|
1559
1559
|
"lineHeight": 1.25,
|
|
1560
1560
|
"autoResize": false
|
|
1561
1561
|
},
|
package/docs/auto-discovery.md
CHANGED
|
@@ -105,9 +105,9 @@ Flip via Dashboard → Settings → Polling → "Granular work-discovery control
|
|
|
105
105
|
|
|
106
106
|
### Source 2: PRD Gap Analysis (via `materializePlansAsWorkItems`)
|
|
107
107
|
|
|
108
|
-
PRD items flow through `materializePlansAsWorkItems()`, which
|
|
108
|
+
PRD items flow through `materializePlansAsWorkItems()`, which queries SQL PRDs for `missing` / `updated` / `planned` items and creates work items in the target project's queue.
|
|
109
109
|
|
|
110
|
-
**Reads:**
|
|
110
|
+
**Reads:** SQL `prds` and `prd_items`.
|
|
111
111
|
|
|
112
112
|
| Item State | Action | Dispatch Type |
|
|
113
113
|
|------------|--------|---------------|
|
|
@@ -182,7 +182,7 @@ priority: high
|
|
|
182
182
|
...
|
|
183
183
|
```
|
|
184
184
|
|
|
185
|
-
**Plans** (`materializePlansAsWorkItems`):
|
|
185
|
+
**Plans** (`materializePlansAsWorkItems`): Queries SQL PRDs for `missing`/`planned` items. Creates work items in the target project's queue with `createdBy: 'engine:plan-discovery'`. Work item ID = PRD item ID (e.g. `P-43e5ac28`). Deduped by `id`.
|
|
186
186
|
|
|
187
187
|
Both write to `work-items.json` and are picked up by Source 3 on the same or next tick.
|
|
188
188
|
|