@cluesmith/codev 3.3.4 → 3.3.5
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/dashboard-dist/assets/index-BuflRUi6.js +220 -0
- package/dashboard-dist/assets/index-BuflRUi6.js.map +1 -0
- package/dashboard-dist/assets/index-r3bZzLmX.css +32 -0
- package/dashboard-dist/index.html +2 -2
- package/dist/agent-farm/commands/send.d.ts +3 -2
- package/dist/agent-farm/commands/send.d.ts.map +1 -1
- package/dist/agent-farm/commands/send.js +24 -5
- package/dist/agent-farm/commands/send.js.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.js +12 -2
- package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
- package/dist/agent-farm/servers/decisions/file.d.ts +50 -0
- package/dist/agent-farm/servers/decisions/file.d.ts.map +1 -0
- package/dist/agent-farm/servers/decisions/file.js +146 -0
- package/dist/agent-farm/servers/decisions/file.js.map +1 -0
- package/dist/agent-farm/servers/decisions/grammar.d.ts +36 -0
- package/dist/agent-farm/servers/decisions/grammar.d.ts.map +1 -0
- package/dist/agent-farm/servers/decisions/grammar.js +109 -0
- package/dist/agent-farm/servers/decisions/grammar.js.map +1 -0
- package/dist/agent-farm/servers/decisions/notice.d.ts +11 -0
- package/dist/agent-farm/servers/decisions/notice.d.ts.map +1 -0
- package/dist/agent-farm/servers/decisions/notice.js +18 -0
- package/dist/agent-farm/servers/decisions/notice.js.map +1 -0
- package/dist/agent-farm/servers/decisions/parse.d.ts +36 -0
- package/dist/agent-farm/servers/decisions/parse.d.ts.map +1 -0
- package/dist/agent-farm/servers/decisions/parse.js +308 -0
- package/dist/agent-farm/servers/decisions/parse.js.map +1 -0
- package/dist/agent-farm/servers/decisions/routes.d.ts +17 -0
- package/dist/agent-farm/servers/decisions/routes.d.ts.map +1 -0
- package/dist/agent-farm/servers/decisions/routes.js +160 -0
- package/dist/agent-farm/servers/decisions/routes.js.map +1 -0
- package/dist/agent-farm/servers/decisions/splice.d.ts +26 -0
- package/dist/agent-farm/servers/decisions/splice.d.ts.map +1 -0
- package/dist/agent-farm/servers/decisions/splice.js +120 -0
- package/dist/agent-farm/servers/decisions/splice.js.map +1 -0
- package/dist/agent-farm/servers/decisions/stamp.d.ts +4 -0
- package/dist/agent-farm/servers/decisions/stamp.d.ts.map +1 -0
- package/dist/agent-farm/servers/decisions/stamp.js +171 -0
- package/dist/agent-farm/servers/decisions/stamp.js.map +1 -0
- package/dist/agent-farm/servers/overview.d.ts +4 -3
- package/dist/agent-farm/servers/overview.d.ts.map +1 -1
- package/dist/agent-farm/servers/overview.js +51 -24
- package/dist/agent-farm/servers/overview.js.map +1 -1
- package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-routes.js +63 -5
- package/dist/agent-farm/servers/tower-routes.js.map +1 -1
- package/dist/agent-farm/utils/message-format.d.ts +8 -0
- package/dist/agent-farm/utils/message-format.d.ts.map +1 -1
- package/dist/agent-farm/utils/message-format.js +16 -0
- package/dist/agent-farm/utils/message-format.js.map +1 -1
- package/dist/commands/consult/index.d.ts +20 -1
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +56 -2
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/porch/checks.d.ts +7 -1
- package/dist/commands/porch/checks.d.ts.map +1 -1
- package/dist/commands/porch/checks.js +77 -14
- package/dist/commands/porch/checks.js.map +1 -1
- package/dist/commands/porch/index.d.ts +38 -1
- package/dist/commands/porch/index.d.ts.map +1 -1
- package/dist/commands/porch/index.js +79 -43
- package/dist/commands/porch/index.js.map +1 -1
- package/dist/commands/porch/protocol.d.ts +4 -3
- package/dist/commands/porch/protocol.d.ts.map +1 -1
- package/dist/commands/porch/protocol.js +32 -5
- package/dist/commands/porch/protocol.js.map +1 -1
- package/dist/commands/porch/types.d.ts +2 -0
- package/dist/commands/porch/types.d.ts.map +1 -1
- package/dist/lib/agent-env.d.ts +10 -1
- package/dist/lib/agent-env.d.ts.map +1 -1
- package/dist/lib/agent-env.js +12 -1
- package/dist/lib/agent-env.js.map +1 -1
- package/dist/lib/config.d.ts +8 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js.map +1 -1
- package/package.json +4 -4
- package/skeleton/.claude/skills/afx/SKILL.md +15 -0
- package/skeleton/.claude/skills/arch-init/SKILL.md +56 -9
- package/skeleton/.claude/skills/arch-save/SKILL.md +100 -9
- package/skeleton/.claude/skills/consult/SKILL.md +9 -0
- package/skeleton/.claude/skills/porch/SKILL.md +18 -0
- package/skeleton/.codex/skills/afx/SKILL.md +15 -0
- package/skeleton/.codex/skills/arch-init/SKILL.md +56 -9
- package/skeleton/.codex/skills/arch-save/SKILL.md +100 -9
- package/skeleton/.codex/skills/consult/SKILL.md +9 -0
- package/skeleton/.codex/skills/porch/SKILL.md +18 -0
- package/skeleton/protocol-schema.json +6 -0
- package/skeleton/protocols/maintain/protocol.md +5 -0
- package/skeleton/protocols/pir/prompts/review.md +0 -1
- package/skeleton/protocols/protocol-schema.json +6 -0
- package/skeleton/resources/commands/agent-farm.md +44 -0
- package/skeleton/resources/commands/consult.md +9 -2
- package/skeleton/templates/cheatsheet.md +1 -1
- package/dashboard-dist/assets/index-DZf6pNPE.js +0 -199
- package/dashboard-dist/assets/index-DZf6pNPE.js.map +0 -1
- package/dashboard-dist/assets/index-DnYLy5C_.css +0 -32
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Ruling splice for the decisions file (Spec 1819).
|
|
2
|
+
//
|
|
3
|
+
// A pure function that writes the owner's ruling into the file's text, or refuses
|
|
4
|
+
// with a specific reason and returns no text. The write route is a thin wrapper
|
|
5
|
+
// around it: read the file, splice, write back only on success.
|
|
6
|
+
//
|
|
7
|
+
// The ruling goes directly after the item's `**Your word:**` line: the word on the
|
|
8
|
+
// very next line; then the comment, after a blank line when a word was written; then
|
|
9
|
+
// a blank line when the line that follows is not already blank. Inserted lines use
|
|
10
|
+
// the file's own line ending, a missing final newline on the Your-word line is
|
|
11
|
+
// supplied, and every other character is preserved in order. The result is
|
|
12
|
+
// re-parsed and must keep the file's structure — anything the explicit checks miss
|
|
13
|
+
// (an option word that is itself a heading or fence, say) is `structure-changed`.
|
|
14
|
+
import { isFenceLine, isHeadingLine, isYourWordLine } from './grammar.js';
|
|
15
|
+
import { parseDecisions } from './parse.js';
|
|
16
|
+
/** Longest comment accepted, in characters, after trimming. */
|
|
17
|
+
export const MAX_COMMENT_LENGTH = 10_000;
|
|
18
|
+
function refuse(code, message) {
|
|
19
|
+
return { ok: false, code, message };
|
|
20
|
+
}
|
|
21
|
+
/** Refuse a comment line that would add structure: a heading, fence or Your-word line. */
|
|
22
|
+
function checkCommentLines(commentLines) {
|
|
23
|
+
for (const [n, line] of commentLines.entries()) {
|
|
24
|
+
const where = `Line ${n + 1} of the comment`;
|
|
25
|
+
if (isHeadingLine(line))
|
|
26
|
+
return refuse('comment-heading', `${where} is a markdown heading ("${line.trim()}"); headings end the item.`);
|
|
27
|
+
if (isFenceLine(line))
|
|
28
|
+
return refuse('comment-fence', `${where} is a code fence ("${line.trim()}"), which would change how the file parses.`);
|
|
29
|
+
if (isYourWordLine(line))
|
|
30
|
+
return refuse('comment-your-word', `${where} starts with **Your word:**, which would give the item a second Your-word line.`);
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
/** Find the request's item in `## Open` and check it can take a ruling now. */
|
|
35
|
+
function checkItem(parsed, request, word) {
|
|
36
|
+
const { id } = request;
|
|
37
|
+
if (parsed.problems.includes('open-multiple')) {
|
|
38
|
+
return refuse('open-multiple', 'The file has more than one ## Open section; the tab cannot tell where items belong, so it writes nothing.');
|
|
39
|
+
}
|
|
40
|
+
const matches = parsed.items.filter(item => item.id === id);
|
|
41
|
+
if (matches.length === 0)
|
|
42
|
+
return refuse('heading-missing', `Item ${id} is not in ## Open (it may have been ruled or removed).`);
|
|
43
|
+
if (matches.length > 1)
|
|
44
|
+
return refuse('heading-duplicate', `Item ${id} appears ${matches.length} times in ## Open; rulings for it go through main.`);
|
|
45
|
+
const item = matches[0];
|
|
46
|
+
if (item.yourWordLines.length === 0)
|
|
47
|
+
return refuse('your-word-missing', `Item ${id} has no **Your word:** line to rule under.`);
|
|
48
|
+
if (item.yourWordLines.length > 1)
|
|
49
|
+
return refuse('your-word-multiple', `Item ${id} has ${item.yourWordLines.length} **Your word:** lines; the tab cannot tell which one to rule under.`);
|
|
50
|
+
if (item.currentRuling !== '')
|
|
51
|
+
return refuse('already-ruled', `Item ${id} already has text under its **Your word:** line; a change goes through main or a hand edit.`);
|
|
52
|
+
if (word && !item.options.some(option => option.word === word)) {
|
|
53
|
+
return refuse('unknown-word', `"${word}" is not an option on item ${id}'s current **Your word:** line.`);
|
|
54
|
+
}
|
|
55
|
+
if (item.fingerprint !== request.fingerprint)
|
|
56
|
+
return refuse('item-changed', `Item ${id} changed since it was shown; review it and submit again.`);
|
|
57
|
+
return item;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Check the spliced text kept the file's structure: the same sections, the same items
|
|
61
|
+
* in the same order, the same options on this item, this item's current ruling equal
|
|
62
|
+
* to the inserted ruling, and every inserted line plain unfenced content.
|
|
63
|
+
*/
|
|
64
|
+
function verify(before, after, item, ruling, insertedLines) {
|
|
65
|
+
const sections = (p) => p.lines.filter(l => l.headingLevel === 1 || l.headingLevel === 2).map(l => `${l.headingLevel} ${l.headingText}`).join('\n');
|
|
66
|
+
const changed = (what) => refuse('structure-changed', `Writing this ruling into item ${item.id} would change ${what}, so nothing was written.`);
|
|
67
|
+
if (sections(after) !== sections(before) || after.problems.join() !== before.problems.join())
|
|
68
|
+
return changed("the file's sections");
|
|
69
|
+
if (after.items.map(i => i.id).join('\n') !== before.items.map(i => i.id).join('\n'))
|
|
70
|
+
return changed('the list of open items');
|
|
71
|
+
const afterItem = after.items.find(i => i.headingLine === item.headingLine);
|
|
72
|
+
if (!afterItem || afterItem.id !== item.id)
|
|
73
|
+
return changed('the list of open items');
|
|
74
|
+
if (JSON.stringify(afterItem.options) !== JSON.stringify(item.options))
|
|
75
|
+
return changed("the item's options");
|
|
76
|
+
if (afterItem.currentRuling !== ruling)
|
|
77
|
+
return changed("the item's text after its **Your word:** line");
|
|
78
|
+
const inserted = after.lines.slice(insertedLines.from, insertedLines.from + insertedLines.count);
|
|
79
|
+
if (inserted.some(l => l.fenced || l.headingLevel > 0 || l.yourWord))
|
|
80
|
+
return changed("the file's structure (the ruling would add a heading or fence)");
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
/** Apply the owner's ruling to the decisions file's text, or refuse with the reason. */
|
|
84
|
+
export function spliceRuling(text, request) {
|
|
85
|
+
if (!request.id)
|
|
86
|
+
return refuse('invalid-request', 'The request names no item.');
|
|
87
|
+
if (!request.fingerprint)
|
|
88
|
+
return refuse('invalid-request', 'The request carries no item fingerprint.');
|
|
89
|
+
const word = request.word ?? '';
|
|
90
|
+
const comment = (request.comment ?? '').trim();
|
|
91
|
+
if (!word && !comment)
|
|
92
|
+
return refuse('empty-ruling', 'Choose an option or write a comment.');
|
|
93
|
+
if (comment.length > MAX_COMMENT_LENGTH) {
|
|
94
|
+
return refuse('comment-too-long', `The comment is ${comment.length} characters; the limit is ${MAX_COMMENT_LENGTH}.`);
|
|
95
|
+
}
|
|
96
|
+
const commentLines = comment ? comment.split(/\r\n|\r|\n/) : [];
|
|
97
|
+
const commentRefusal = checkCommentLines(commentLines);
|
|
98
|
+
if (commentRefusal)
|
|
99
|
+
return commentRefusal;
|
|
100
|
+
const before = parseDecisions(text);
|
|
101
|
+
const item = checkItem(before, request, word);
|
|
102
|
+
if ('ok' in item)
|
|
103
|
+
return item;
|
|
104
|
+
const rulingLines = [...(word ? [word] : []), ...(word && comment ? [''] : []), ...commentLines];
|
|
105
|
+
const yourWord = before.lines[item.yourWordLines[0]];
|
|
106
|
+
const following = before.lines[yourWord.index + 1];
|
|
107
|
+
const newLines = following && following.text.trim() !== '' ? [...rulingLines, ''] : rulingLines;
|
|
108
|
+
const at = yourWord.end + yourWord.eol.length;
|
|
109
|
+
const inserted = (yourWord.eol === '' ? before.eol : '') + newLines.map(line => line + before.eol).join('');
|
|
110
|
+
const result = text.slice(0, at) + inserted + text.slice(at);
|
|
111
|
+
// The parser's current ruling is LF-joined whatever the file's line ending.
|
|
112
|
+
const refusal = verify(before, parseDecisions(result), item, rulingLines.join('\n'), {
|
|
113
|
+
from: yourWord.index + 1,
|
|
114
|
+
count: newLines.length,
|
|
115
|
+
});
|
|
116
|
+
if (refusal)
|
|
117
|
+
return refusal;
|
|
118
|
+
return { ok: true, text: result };
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=splice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"splice.js","sourceRoot":"","sources":["../../../../src/agent-farm/servers/decisions/splice.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stamp.d.ts","sourceRoot":"","sources":["../../../../src/agent-farm/servers/decisions/stamp.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AA2H5D,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAuCvD"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// Decision heading timestamps (Spec 1819).
|
|
2
|
+
//
|
|
3
|
+
// A stamp is `YYYY-MM-DD h:mm am|pm <ZONE>` or `YYYY-MM-DD HH:mm <ZONE>`. It is
|
|
4
|
+
// resolved to an absolute instant using ONLY the zone written in it — nothing here
|
|
5
|
+
// defaults to, or special-cases, any zone. Anything not in the accepted forms is
|
|
6
|
+
// returned unconverted with a reason, so the tab shows it verbatim.
|
|
7
|
+
//
|
|
8
|
+
// IANA and generic zones are resolved with the runtime's built-in `Intl` time-zone
|
|
9
|
+
// data by offset probing: the wall-clock time is tried against every offset the
|
|
10
|
+
// zone has near that date, and the candidates that round-trip are kept. One means
|
|
11
|
+
// success; none is a spring-forward gap; two is a fall-back fold. Gaps and folds
|
|
12
|
+
// are left unconverted rather than broken by a tie-break rule.
|
|
13
|
+
/** Generic North-American zones: DST follows the stamp's date. */
|
|
14
|
+
const GENERIC_ZONES = {
|
|
15
|
+
PT: 'America/Los_Angeles',
|
|
16
|
+
MT: 'America/Denver',
|
|
17
|
+
CT: 'America/Chicago',
|
|
18
|
+
ET: 'America/New_York',
|
|
19
|
+
AKT: 'America/Anchorage',
|
|
20
|
+
HT: 'Pacific/Honolulu',
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Fixed-offset abbreviations, in minutes east of UTC. `CST` is North-American
|
|
24
|
+
* Central (China Standard Time is written `+08:00` or `Asia/Shanghai`). Ambiguous
|
|
25
|
+
* abbreviations (`IST`, `BST`, `AST`, …) are deliberately absent.
|
|
26
|
+
*/
|
|
27
|
+
const FIXED_ZONES = {
|
|
28
|
+
UTC: 0,
|
|
29
|
+
GMT: 0,
|
|
30
|
+
Z: 0,
|
|
31
|
+
PST: -8 * 60,
|
|
32
|
+
PDT: -7 * 60,
|
|
33
|
+
MST: -7 * 60,
|
|
34
|
+
MDT: -6 * 60,
|
|
35
|
+
CST: -6 * 60,
|
|
36
|
+
CDT: -5 * 60,
|
|
37
|
+
EST: -5 * 60,
|
|
38
|
+
EDT: -4 * 60,
|
|
39
|
+
AKST: -9 * 60,
|
|
40
|
+
AKDT: -8 * 60,
|
|
41
|
+
HST: -10 * 60,
|
|
42
|
+
WET: 0,
|
|
43
|
+
WEST: 1 * 60,
|
|
44
|
+
CET: 1 * 60,
|
|
45
|
+
CEST: 2 * 60,
|
|
46
|
+
EET: 2 * 60,
|
|
47
|
+
EEST: 3 * 60,
|
|
48
|
+
MSK: 3 * 60,
|
|
49
|
+
SGT: 8 * 60,
|
|
50
|
+
HKT: 8 * 60,
|
|
51
|
+
JST: 9 * 60,
|
|
52
|
+
KST: 9 * 60,
|
|
53
|
+
AEST: 10 * 60,
|
|
54
|
+
AEDT: 11 * 60,
|
|
55
|
+
NZST: 12 * 60,
|
|
56
|
+
NZDT: 13 * 60,
|
|
57
|
+
};
|
|
58
|
+
const STAMP_RE = /^(\d{4})-(\d{2})-(\d{2})\s+(\d{1,2}):(\d{2})(?:\s*(am|pm))?\s+(\S.*?)\s*$/i;
|
|
59
|
+
const OFFSET_RE = /^(?:UTC|GMT)?([+-])(\d{1,2})(?::?(\d{2}))?$/;
|
|
60
|
+
const MINUTE_MS = 60_000;
|
|
61
|
+
const DAY_MS = 24 * 60 * MINUTE_MS;
|
|
62
|
+
function isIanaZone(name) {
|
|
63
|
+
try {
|
|
64
|
+
new Intl.DateTimeFormat('en-US', { timeZone: name });
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function parseZone(zone) {
|
|
72
|
+
// Own keys only: `in` would let prototype names (`toString`, `__proto__`, …) through.
|
|
73
|
+
if (Object.hasOwn(FIXED_ZONES, zone))
|
|
74
|
+
return { kind: 'fixed', offsetMinutes: FIXED_ZONES[zone] };
|
|
75
|
+
if (Object.hasOwn(GENERIC_ZONES, zone))
|
|
76
|
+
return { kind: 'iana', name: GENERIC_ZONES[zone] };
|
|
77
|
+
const offset = OFFSET_RE.exec(zone);
|
|
78
|
+
if (offset) {
|
|
79
|
+
const hours = Number(offset[2]);
|
|
80
|
+
const minutes = offset[3] ? Number(offset[3]) : 0;
|
|
81
|
+
if (hours > 14 || minutes > 59)
|
|
82
|
+
return null;
|
|
83
|
+
const sign = offset[1] === '-' ? -1 : 1;
|
|
84
|
+
return { kind: 'fixed', offsetMinutes: sign * (hours * 60 + minutes) };
|
|
85
|
+
}
|
|
86
|
+
// IANA names only in Area/Location form, so legacy ids (`EST5EDT`, `IST`, …) are
|
|
87
|
+
// never accepted through the back door.
|
|
88
|
+
if (zone.includes('/') && isIanaZone(zone))
|
|
89
|
+
return { kind: 'iana', name: zone };
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
const formatters = new Map();
|
|
93
|
+
function formatterFor(zone) {
|
|
94
|
+
let f = formatters.get(zone);
|
|
95
|
+
if (!f) {
|
|
96
|
+
f = new Intl.DateTimeFormat('en-US', {
|
|
97
|
+
timeZone: zone,
|
|
98
|
+
hourCycle: 'h23',
|
|
99
|
+
year: 'numeric',
|
|
100
|
+
month: '2-digit',
|
|
101
|
+
day: '2-digit',
|
|
102
|
+
hour: '2-digit',
|
|
103
|
+
minute: '2-digit',
|
|
104
|
+
});
|
|
105
|
+
formatters.set(zone, f);
|
|
106
|
+
}
|
|
107
|
+
return f;
|
|
108
|
+
}
|
|
109
|
+
/** The zone's UTC offset, in minutes, at the given instant. */
|
|
110
|
+
function offsetAt(instantMs, zone) {
|
|
111
|
+
const parts = formatterFor(zone).formatToParts(new Date(instantMs));
|
|
112
|
+
const get = (type) => Number(parts.find(p => p.type === type).value);
|
|
113
|
+
const wallMs = Date.UTC(get('year'), get('month') - 1, get('day'), get('hour'), get('minute'));
|
|
114
|
+
const instantMinute = Math.floor(instantMs / MINUTE_MS) * MINUTE_MS;
|
|
115
|
+
return Math.round((wallMs - instantMinute) / MINUTE_MS);
|
|
116
|
+
}
|
|
117
|
+
/** All instants whose wall-clock time in `zone` is `wallMs` (as if UTC). */
|
|
118
|
+
function instantsForWallClock(wallMs, zone) {
|
|
119
|
+
const offsets = new Set([offsetAt(wallMs - DAY_MS, zone), offsetAt(wallMs, zone), offsetAt(wallMs + DAY_MS, zone)]);
|
|
120
|
+
const instants = new Set();
|
|
121
|
+
for (const offset of offsets) {
|
|
122
|
+
const candidate = wallMs - offset * MINUTE_MS;
|
|
123
|
+
if (offsetAt(candidate, zone) === offset)
|
|
124
|
+
instants.add(candidate);
|
|
125
|
+
}
|
|
126
|
+
return [...instants].sort((a, b) => a - b);
|
|
127
|
+
}
|
|
128
|
+
/** Resolve a heading stamp to an instant using only the zone written in it. */
|
|
129
|
+
export function resolveStamp(raw) {
|
|
130
|
+
const m = STAMP_RE.exec(raw.trim());
|
|
131
|
+
if (!m)
|
|
132
|
+
return { raw, instant: null, unconvertedReason: 'unrecognized-format' };
|
|
133
|
+
const [, y, mo, d, h, mi, meridiem, zoneText] = m;
|
|
134
|
+
// `12:10 am` with no zone backtracks to read `am` as the zone; it has none.
|
|
135
|
+
if (/^(am|pm)$/i.test(zoneText))
|
|
136
|
+
return { raw, instant: null, unconvertedReason: 'unrecognized-format' };
|
|
137
|
+
const year = Number(y);
|
|
138
|
+
const month = Number(mo);
|
|
139
|
+
const day = Number(d);
|
|
140
|
+
let hour = Number(h);
|
|
141
|
+
const minute = Number(mi);
|
|
142
|
+
if (meridiem) {
|
|
143
|
+
if (hour < 1 || hour > 12)
|
|
144
|
+
return { raw, instant: null, unconvertedReason: 'unrecognized-format' };
|
|
145
|
+
const pm = meridiem.toLowerCase() === 'pm';
|
|
146
|
+
hour = (hour % 12) + (pm ? 12 : 0);
|
|
147
|
+
}
|
|
148
|
+
else if (hour > 23) {
|
|
149
|
+
return { raw, instant: null, unconvertedReason: 'unrecognized-format' };
|
|
150
|
+
}
|
|
151
|
+
if (minute > 59)
|
|
152
|
+
return { raw, instant: null, unconvertedReason: 'unrecognized-format' };
|
|
153
|
+
const wallMs = Date.UTC(year, month - 1, day, hour, minute);
|
|
154
|
+
const check = new Date(wallMs);
|
|
155
|
+
if (check.getUTCFullYear() !== year || check.getUTCMonth() !== month - 1 || check.getUTCDate() !== day) {
|
|
156
|
+
return { raw, instant: null, unconvertedReason: 'unrecognized-format' };
|
|
157
|
+
}
|
|
158
|
+
const zone = parseZone(zoneText);
|
|
159
|
+
if (!zone)
|
|
160
|
+
return { raw, instant: null, unconvertedReason: 'unknown-zone' };
|
|
161
|
+
if (zone.kind === 'fixed') {
|
|
162
|
+
return { raw, instant: new Date(wallMs - zone.offsetMinutes * MINUTE_MS).toISOString() };
|
|
163
|
+
}
|
|
164
|
+
const instants = instantsForWallClock(wallMs, zone.name);
|
|
165
|
+
if (instants.length === 0)
|
|
166
|
+
return { raw, instant: null, unconvertedReason: 'nonexistent-time' };
|
|
167
|
+
if (instants.length > 1)
|
|
168
|
+
return { raw, instant: null, unconvertedReason: 'ambiguous-time' };
|
|
169
|
+
return { raw, instant: new Date(instants[0]).toISOString() };
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=stamp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stamp.js","sourceRoot":"","sources":["../../../../src/agent-farm/servers/decisions/stamp.ts"],"names":[],"mappings":"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"}
|
|
@@ -58,6 +58,7 @@ export declare function calculateProgress(parsed: ParsedStatus, workspaceRoot?:
|
|
|
58
58
|
* Each phase gets an equal share of 100%, with 'verified'/'complete' always = 100.
|
|
59
59
|
*/
|
|
60
60
|
export declare function calculateEvenProgress(phase: string, phases: string[]): number;
|
|
61
|
+
/** Human-readable label for the gate the builder is blocked on, or null. */
|
|
61
62
|
export declare function detectBlocked(parsed: ParsedStatus): string | null;
|
|
62
63
|
/**
|
|
63
64
|
* Canonical gate name (e.g. "plan-approval") for the gate the builder is
|
|
@@ -69,9 +70,9 @@ export declare function detectBlockedGate(parsed: ParsedStatus): string | null;
|
|
|
69
70
|
* Detect when the current blocked gate was first requested.
|
|
70
71
|
* Returns the ISO timestamp string or null if not blocked.
|
|
71
72
|
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
73
|
+
* Shares `findBlockedGate` with `detectBlocked` / `detectBlockedGate` so the
|
|
74
|
+
* gate set lives in exactly one place (#927 removed a previously-separate
|
|
75
|
+
* hardcoded array, which was a silent drift hazard).
|
|
75
76
|
*/
|
|
76
77
|
export declare function detectBlockedSince(parsed: ParsedStatus): string | null;
|
|
77
78
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overview.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/overview.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAYH,OAAO,KAAK,EAAW,kBAAkB,EAAmB,MAAM,qBAAqB,CAAC;AACxF,OAAO,EAAkC,KAAK,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAGpG,OAAO,KAAK,EACV,SAAS,EACT,eAAe,EAEf,mBAAmB,EAEnB,YAAY,EACb,MAAM,wBAAwB,CAAC;AAUhC,UAAU,YAAY;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,eAAe,EAAE,OAAO,GAAG,IAAI,CAAC;IAChC;;;;;;;;;;;OAWG;IACH,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,CAyJ7D;AAgBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CActF;AA6BD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAK7E;
|
|
1
|
+
{"version":3,"file":"overview.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/overview.ts"],"names":[],"mappings":"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"}
|
|
@@ -280,13 +280,17 @@ function loadProtocolPhases(workspaceRoot, protocolName) {
|
|
|
280
280
|
}
|
|
281
281
|
/**
|
|
282
282
|
* Detect if a builder is blocked on a gate (requested but not approved).
|
|
283
|
-
* Returns a human-readable label or null.
|
|
284
283
|
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
284
|
+
* Generic over gate names (#1777): ANY gate in `status.yaml` with
|
|
285
|
+
* `status: pending` and a `requested_at` timestamp is a human gate porch is
|
|
286
|
+
* holding the lane at, so it surfaces on `OverviewBuilder.blocked` and the
|
|
287
|
+
* downstream UIs (VSCode Agents tree/toast/status bar, dashboard
|
|
288
|
+
* NeedsAttentionList, Tower hub attention rows) whatever protocol emitted it.
|
|
289
|
+
* The earlier allowlist made EXPERIMENT's `experiment-complete` (and
|
|
290
|
+
* MAINTAIN/RESEARCH's gates) invisible.
|
|
291
|
+
*
|
|
292
|
+
* `GATE_LABELS` supplies display labels and the scan priority for the bundled
|
|
293
|
+
* gates; an unlisted gate is still detected, labelled from its own name.
|
|
290
294
|
*/
|
|
291
295
|
const GATE_LABELS = {
|
|
292
296
|
'spec-approval': 'spec review',
|
|
@@ -299,43 +303,57 @@ const GATE_LABELS = {
|
|
|
299
303
|
// bar). The `pr` gate stays mapped here too (VSCode depends on it); the
|
|
300
304
|
// dashboard alone excludes `pr` from its gate rows since it renders PR rows.
|
|
301
305
|
'verify-approval': 'verify review',
|
|
306
|
+
'experiment-complete': 'experiment review',
|
|
307
|
+
'maintain-complete': 'maintenance review',
|
|
308
|
+
'scope-approval': 'scope review',
|
|
309
|
+
'research-complete': 'research review',
|
|
302
310
|
};
|
|
303
|
-
|
|
304
|
-
|
|
311
|
+
/** Display label for a gate: the mapped label, else derived from its name. */
|
|
312
|
+
function gateLabel(gate) {
|
|
313
|
+
return GATE_LABELS[gate] ?? `${gate.replace(/-(approval|complete|review)$/, '').replace(/-/g, ' ')} review`;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Canonical name of the gate the builder is blocked on, or null. Known gates
|
|
317
|
+
* are scanned first (preserving their priority order), then any other gate in
|
|
318
|
+
* `status.yaml` order.
|
|
319
|
+
*/
|
|
320
|
+
function findBlockedGate(parsed) {
|
|
321
|
+
const gates = new Set([...Object.keys(GATE_LABELS), ...Object.keys(parsed.gates)]);
|
|
322
|
+
for (const gate of gates) {
|
|
305
323
|
if (parsed.gates[gate] === 'pending' && parsed.gateRequestedAt[gate]) {
|
|
306
|
-
return
|
|
324
|
+
return gate;
|
|
307
325
|
}
|
|
308
326
|
}
|
|
309
327
|
return null;
|
|
310
328
|
}
|
|
329
|
+
/** Human-readable label for the gate the builder is blocked on, or null. */
|
|
330
|
+
export function detectBlocked(parsed) {
|
|
331
|
+
const gate = findBlockedGate(parsed);
|
|
332
|
+
if (!gate)
|
|
333
|
+
return null;
|
|
334
|
+
return gateLabel(gate);
|
|
335
|
+
}
|
|
311
336
|
/**
|
|
312
337
|
* Canonical gate name (e.g. "plan-approval") for the gate the builder is
|
|
313
338
|
* blocked on. Sibling to `detectBlocked` which returns the display label.
|
|
314
339
|
* Returns null if the builder isn't blocked.
|
|
315
340
|
*/
|
|
316
341
|
export function detectBlockedGate(parsed) {
|
|
317
|
-
|
|
318
|
-
if (parsed.gates[gate] === 'pending' && parsed.gateRequestedAt[gate]) {
|
|
319
|
-
return gate;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
return null;
|
|
342
|
+
return findBlockedGate(parsed);
|
|
323
343
|
}
|
|
324
344
|
/**
|
|
325
345
|
* Detect when the current blocked gate was first requested.
|
|
326
346
|
* Returns the ISO timestamp string or null if not blocked.
|
|
327
347
|
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
348
|
+
* Shares `findBlockedGate` with `detectBlocked` / `detectBlockedGate` so the
|
|
349
|
+
* gate set lives in exactly one place (#927 removed a previously-separate
|
|
350
|
+
* hardcoded array, which was a silent drift hazard).
|
|
331
351
|
*/
|
|
332
352
|
export function detectBlockedSince(parsed) {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
return null;
|
|
353
|
+
const gate = findBlockedGate(parsed);
|
|
354
|
+
if (!gate)
|
|
355
|
+
return null;
|
|
356
|
+
return parsed.gateRequestedAt[gate];
|
|
339
357
|
}
|
|
340
358
|
/**
|
|
341
359
|
* Compute the canonical "PR ready for human review" signal for a builder.
|
|
@@ -483,6 +501,15 @@ export function extractProjectIdFromWorktreeName(dirName) {
|
|
|
483
501
|
const pirMatch = dirName.match(/^pir-(\d+)/);
|
|
484
502
|
if (pirMatch)
|
|
485
503
|
return pirMatch[1];
|
|
504
|
+
// task-NAvW, worktree-foIg → null (soft mode). Checked before the generic
|
|
505
|
+
// case below because a short id can be all digits (task-1234).
|
|
506
|
+
if (/^(task|worktree)-/.test(dirName))
|
|
507
|
+
return null;
|
|
508
|
+
// Any other numbered protocol spawn (experiment/maintain/research, #1777):
|
|
509
|
+
// experiment-1560 → "1560" (porch project dir is 1560-slug, like SPIR).
|
|
510
|
+
const genericMatch = dirName.match(/^[a-z]+-(\d+)(?:-|$)/);
|
|
511
|
+
if (genericMatch)
|
|
512
|
+
return genericMatch[1];
|
|
486
513
|
// Legacy numeric: 0110 or 0110-slug → "0110"
|
|
487
514
|
const numericMatch = dirName.match(/^(\d+)(?:-|$)/);
|
|
488
515
|
if (numericMatch)
|