@jaimevalasek/aioson 1.36.0 → 1.37.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/CHANGELOG.md +28 -0
- package/docs/en/1-understand/ecosystem-map.md +1 -1
- package/docs/en/1-understand/glossary.md +1 -1
- package/docs/en/2-start/first-project.md +1 -1
- package/docs/en/2-start/initial-decisions.md +4 -2
- package/docs/en/3-recipes/from-idea-to-prd-via-briefing.md +8 -1
- package/docs/en/3-recipes/full-feature-with-sheldon.md +3 -1
- package/docs/en/4-agents/README.md +6 -3
- package/docs/en/4-agents/briefing-refiner.md +146 -0
- package/docs/en/5-reference/README.md +1 -0
- package/docs/en/5-reference/autopilot-handoff.md +286 -0
- package/docs/en/5-reference/cli-reference.md +6 -0
- package/docs/pt/1-entender/glossario.md +1 -1
- package/docs/pt/1-entender/mapa-do-ecossistema.md +1 -1
- package/docs/pt/2-comecar/decisoes-iniciais.md +4 -2
- package/docs/pt/2-comecar/primeiro-projeto.md +1 -1
- package/docs/pt/3-receitas/da-ideia-ao-prd-via-briefing.md +8 -1
- package/docs/pt/3-receitas/feature-completa-com-sheldon.md +3 -1
- package/docs/pt/4-agentes/README.md +13 -11
- package/docs/pt/4-agentes/briefing-refiner.md +64 -40
- package/docs/pt/4-agentes/briefing.md +6 -1
- package/docs/pt/4-agentes/dev.md +19 -1
- package/docs/pt/4-agentes/deyvin.md +4 -0
- package/docs/pt/4-agentes/discover.md +4 -0
- package/docs/pt/4-agentes/neo.md +4 -0
- package/docs/pt/4-agentes/orache.md +6 -0
- package/docs/pt/4-agentes/orchestrator.md +12 -0
- package/docs/pt/4-agentes/pentester.md +6 -0
- package/docs/pt/4-agentes/product.md +19 -1
- package/docs/pt/4-agentes/qa.md +10 -2
- package/docs/pt/4-agentes/setup.md +3 -1
- package/docs/pt/4-agentes/sheldon.md +12 -0
- package/docs/pt/4-agentes/tester.md +6 -0
- package/docs/pt/4-agentes/ux-ui.md +2 -1
- package/docs/pt/5-referencia/README.md +1 -1
- package/docs/pt/5-referencia/agent-chain-continuity.md +1 -1
- package/docs/pt/5-referencia/autopilot-handoff.md +191 -74
- package/docs/pt/5-referencia/comandos-cli.md +16 -7
- package/docs/pt/5-referencia/skills.md +2 -0
- package/docs/pt/agentes.md +3 -1
- package/package.json +1 -1
- package/src/agents.js +1 -1
- package/src/artifact-kinds.js +2 -1
- package/src/autopilot-signal.js +71 -0
- package/src/cli.js +9 -1
- package/src/commands/agents.js +18 -2
- package/src/commands/briefing.js +337 -1
- package/src/commands/feature-close.js +136 -43
- package/src/commands/live.js +47 -11
- package/src/commands/update.js +5 -1
- package/src/commands/verification-plan.js +28 -1
- package/src/commands/verify-artifact.js +64 -1
- package/src/commands/workflow-execute.js +149 -31
- package/src/commands/workflow-next.js +60 -16
- package/src/doctor.js +4 -2
- package/src/i18n/messages/en.js +2 -1
- package/src/i18n/messages/es.js +2 -1
- package/src/i18n/messages/fr.js +2 -1
- package/src/i18n/messages/pt-BR.js +2 -1
- package/src/lib/briefing-refiner/apply-feedback.js +18 -4
- package/src/lib/briefing-refiner/feedback-schema.js +73 -4
- package/src/lib/briefing-refiner/refinement-report.js +11 -0
- package/src/lib/briefing-refiner/review-html.js +388 -68
- package/src/parser.js +6 -0
- package/template/.aioson/agents/briefing-refiner.md +87 -47
- package/template/.aioson/agents/briefing.md +4 -0
- package/template/.aioson/agents/dev.md +9 -2
- package/template/.aioson/agents/deyvin.md +4 -0
- package/template/.aioson/agents/discover.md +4 -0
- package/template/.aioson/agents/neo.md +4 -0
- package/template/.aioson/agents/orache.md +4 -0
- package/template/.aioson/agents/orchestrator.md +16 -0
- package/template/.aioson/agents/pentester.md +4 -0
- package/template/.aioson/agents/product.md +25 -1
- package/template/.aioson/agents/qa.md +5 -1
- package/template/.aioson/agents/sheldon.md +9 -1
- package/template/.aioson/agents/tester.md +4 -0
- package/template/.aioson/agents/ux-ui.md +1 -1
- package/template/.aioson/docs/agent-help.md +126 -0
- package/template/.aioson/docs/autopilot-handoff.md +26 -16
- package/template/.aioson/docs/dev/phase-loop.md +8 -5
- package/template/.aioson/docs/play/llm-data-and-bindings.md +70 -7
- package/template/.aioson/skills/design/interface-design/SKILL.md +17 -0
- package/template/AGENTS.md +36 -36
- package/template/CLAUDE.md +1 -1
|
@@ -19,14 +19,166 @@ function safeJson(value) {
|
|
|
19
19
|
return JSON.stringify(value).replace(/</g, '\\u003c');
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
const FINDING_CATEGORIES = ['ambiguity', 'redundancy', 'gap', 'risk', 'pending-decision', 'scope-suggestion'];
|
|
23
|
+
|
|
24
|
+
// UI strings for the generated surface. The surface is user-facing, so it follows
|
|
25
|
+
// the interaction language (locale from the CLI); schema enum values (statuses,
|
|
26
|
+
// categories) stay canonical English because they round-trip into the JSON.
|
|
27
|
+
const LABELS = {
|
|
28
|
+
en: {
|
|
29
|
+
title: 'Briefing Review',
|
|
30
|
+
round: 'round',
|
|
31
|
+
source: 'source',
|
|
32
|
+
notes: 'Notes',
|
|
33
|
+
note_placeholder: 'Plain text note for this section',
|
|
34
|
+
finding_note_placeholder: 'Decision / note for this finding',
|
|
35
|
+
findings_title: 'Audit findings',
|
|
36
|
+
other_findings: 'Findings without a matching section',
|
|
37
|
+
recommendation: 'Recommendation',
|
|
38
|
+
blocking_chip: 'blocking',
|
|
39
|
+
summary_title: 'Review State',
|
|
40
|
+
summary_changed: 'Changed sections',
|
|
41
|
+
summary_blocked: 'Blocking items',
|
|
42
|
+
summary_notes: 'Notes',
|
|
43
|
+
summary_pending_findings: 'Pending findings',
|
|
44
|
+
filters_title: 'Finding filters',
|
|
45
|
+
filter_all: 'all',
|
|
46
|
+
btn_download: 'Download JSON',
|
|
47
|
+
btn_copy: 'Copy JSON',
|
|
48
|
+
btn_save: 'Save to file',
|
|
49
|
+
btn_copy_path: 'Copy path',
|
|
50
|
+
target_hint: 'Save target:',
|
|
51
|
+
toolbar_hint: 'Save writes structured feedback only. HTML edits are not canonical until exported.',
|
|
52
|
+
hint_title: 'How to use',
|
|
53
|
+
hint_body: [
|
|
54
|
+
'Open this file in a real browser (double-click it) — editor previews are sandboxed and block saving and downloads.',
|
|
55
|
+
'Edit section text, set a status per section, decide each finding, add notes.',
|
|
56
|
+
'Then return the JSON one of three ways: “Save to file” directly over {feedbackPath}; “Download JSON” and move it over {feedbackPath}; or “Copy JSON” and paste it in the chat when you reactivate @briefing-refiner.',
|
|
57
|
+
'Your edits autosave to this browser (local draft) — closing the tab loses nothing.'
|
|
58
|
+
],
|
|
59
|
+
blocks_title: 'What blocks the PRD',
|
|
60
|
+
blocks_body: 'Any section marked blocked, and any blocking finding still pending.',
|
|
61
|
+
// runtime strings
|
|
62
|
+
section_blocked: 'Section marked as blocked',
|
|
63
|
+
blocks_prd: 'Blocks PRD:',
|
|
64
|
+
autosaved: 'Draft autosaved locally at',
|
|
65
|
+
draft_restored: 'Local draft restored',
|
|
66
|
+
discard_draft: 'Discard draft',
|
|
67
|
+
downloaded: 'Downloaded refinement-feedback.json — move it over the existing file in the briefing folder.',
|
|
68
|
+
copied: 'JSON copied — paste it in the chat or into refinement-feedback.json.',
|
|
69
|
+
saved: 'Saved refinement-feedback.json.',
|
|
70
|
+
path_copied: 'Path copied — paste it into the save dialog’s "File name" field to jump straight to the folder.',
|
|
71
|
+
no_fsa: 'Direct save unavailable in this browser; downloaded JSON instead.',
|
|
72
|
+
sandbox_fallback: 'Direct save blocked here (sandboxed preview) — downloaded JSON instead. Open this file in a real browser for direct save.'
|
|
73
|
+
},
|
|
74
|
+
pt: {
|
|
75
|
+
title: 'Revisão do Briefing',
|
|
76
|
+
round: 'rodada',
|
|
77
|
+
source: 'fonte',
|
|
78
|
+
notes: 'Notas',
|
|
79
|
+
note_placeholder: 'Nota em texto puro para esta seção',
|
|
80
|
+
finding_note_placeholder: 'Decisão / nota para este achado',
|
|
81
|
+
findings_title: 'Achados da auditoria',
|
|
82
|
+
other_findings: 'Achados sem seção correspondente',
|
|
83
|
+
recommendation: 'Recomendação',
|
|
84
|
+
blocking_chip: 'bloqueante',
|
|
85
|
+
summary_title: 'Estado da revisão',
|
|
86
|
+
summary_changed: 'Seções alteradas',
|
|
87
|
+
summary_blocked: 'Itens bloqueantes',
|
|
88
|
+
summary_notes: 'Notas',
|
|
89
|
+
summary_pending_findings: 'Achados pendentes',
|
|
90
|
+
filters_title: 'Filtros de achados',
|
|
91
|
+
filter_all: 'todos',
|
|
92
|
+
btn_download: 'Baixar JSON',
|
|
93
|
+
btn_copy: 'Copiar JSON',
|
|
94
|
+
btn_save: 'Salvar no arquivo',
|
|
95
|
+
btn_copy_path: 'Copiar caminho',
|
|
96
|
+
target_hint: 'Destino:',
|
|
97
|
+
toolbar_hint: 'Salvar grava apenas o feedback estruturado. Edições no HTML não são canônicas até exportar.',
|
|
98
|
+
hint_title: 'Como usar',
|
|
99
|
+
hint_body: [
|
|
100
|
+
'Abra este arquivo num navegador de verdade (duplo clique) — previews de editor são sandboxed e bloqueiam salvar e baixar.',
|
|
101
|
+
'Edite o texto das seções, marque o status de cada uma, decida cada achado e anote comentários.',
|
|
102
|
+
'Depois devolva o JSON de uma destas formas: “Salvar no arquivo” direto sobre {feedbackPath}; “Baixar JSON” e mover por cima de {feedbackPath}; ou “Copiar JSON” e colar no chat ao reativar o @briefing-refiner.',
|
|
103
|
+
'Suas edições são salvas automaticamente neste navegador (rascunho local) — fechar a aba não perde nada.'
|
|
104
|
+
],
|
|
105
|
+
blocks_title: 'O que bloqueia o PRD',
|
|
106
|
+
blocks_body: 'Qualquer seção marcada como blocked e qualquer achado bloqueante ainda pendente.',
|
|
107
|
+
// runtime strings
|
|
108
|
+
section_blocked: 'Seção marcada como bloqueada',
|
|
109
|
+
blocks_prd: 'Bloqueia o PRD:',
|
|
110
|
+
autosaved: 'Rascunho salvo localmente às',
|
|
111
|
+
draft_restored: 'Rascunho local restaurado',
|
|
112
|
+
discard_draft: 'Descartar rascunho',
|
|
113
|
+
downloaded: 'refinement-feedback.json baixado — mova por cima do arquivo existente na pasta do briefing.',
|
|
114
|
+
copied: 'JSON copiado — cole no chat ou dentro de refinement-feedback.json.',
|
|
115
|
+
saved: 'refinement-feedback.json salvo.',
|
|
116
|
+
path_copied: 'Caminho copiado — cole no campo "Nome do arquivo" da janela de salvar para ir direto à pasta.',
|
|
117
|
+
no_fsa: 'Salvamento direto indisponível neste navegador; o JSON foi baixado.',
|
|
118
|
+
sandbox_fallback: 'Salvamento direto bloqueado aqui (preview sandboxed) — o JSON foi baixado. Abra este arquivo num navegador de verdade para salvar direto.'
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
function resolveLabels(locale) {
|
|
123
|
+
return String(locale || '').toLowerCase().startsWith('pt') ? LABELS.pt : LABELS.en;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function renderFinding(finding, labels) {
|
|
127
|
+
const rec = finding.recommendation
|
|
128
|
+
? `<div class="f-rec"><b>${escapeHtml(labels.recommendation)}:</b> ${escapeHtml(finding.recommendation)}</div>`
|
|
129
|
+
: '';
|
|
130
|
+
const blockChip = finding.blocking ? `<span class="chip chip-block">${escapeHtml(labels.blocking_chip)}</span>` : '';
|
|
131
|
+
return `
|
|
132
|
+
<div class="finding" data-finding="${escapeHtml(finding.id)}" data-cat="${escapeHtml(finding.category)}">
|
|
133
|
+
<div class="f-head">
|
|
134
|
+
<span class="chip chip-cat">${escapeHtml(finding.category)}</span>
|
|
135
|
+
<span class="chip chip-sev-${escapeHtml(finding.severity)}">${escapeHtml(finding.severity)}</span>
|
|
136
|
+
${blockChip}
|
|
137
|
+
<span class="f-id">${escapeHtml(finding.id)}</span>
|
|
138
|
+
<select data-role="f-status">
|
|
139
|
+
<option value="pending"${finding.status === 'pending' ? ' selected' : ''}>pending</option>
|
|
140
|
+
<option value="accepted"${finding.status === 'accepted' ? ' selected' : ''}>accepted</option>
|
|
141
|
+
<option value="rejected"${finding.status === 'rejected' ? ' selected' : ''}>rejected</option>
|
|
142
|
+
<option value="deferred"${finding.status === 'deferred' ? ' selected' : ''}>deferred</option>
|
|
143
|
+
</select>
|
|
144
|
+
</div>
|
|
145
|
+
<div class="f-text">${escapeHtml(finding.text)}</div>
|
|
146
|
+
${rec}
|
|
147
|
+
<input class="f-note" data-role="f-note" type="text" value="${escapeHtml(finding.note || '')}" placeholder="${escapeHtml(labels.finding_note_placeholder)}">
|
|
148
|
+
</div>`;
|
|
149
|
+
}
|
|
150
|
+
|
|
22
151
|
function buildReviewHtml(data) {
|
|
23
152
|
const feedback = data.feedback;
|
|
153
|
+
const labels = resolveLabels(data.locale);
|
|
24
154
|
const sections = feedback.sections || [];
|
|
155
|
+
const findings = feedback.findings || [];
|
|
156
|
+
const feedbackPath = `.aioson/briefings/${feedback.briefing_slug}/refinement-feedback.json`;
|
|
157
|
+
const feedbackAbsPath = data.feedbackAbsPath || '';
|
|
158
|
+
|
|
159
|
+
const findingsBySection = new Map();
|
|
160
|
+
const orphanFindings = [];
|
|
161
|
+
const sectionIds = new Set(sections.map((section) => section.id));
|
|
162
|
+
for (const finding of findings) {
|
|
163
|
+
if (sectionIds.has(finding.section_id)) {
|
|
164
|
+
if (!findingsBySection.has(finding.section_id)) findingsBySection.set(finding.section_id, []);
|
|
165
|
+
findingsBySection.get(finding.section_id).push(finding);
|
|
166
|
+
} else {
|
|
167
|
+
orphanFindings.push(finding);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
25
171
|
const nav = sections.map((section) => `<a href="#${escapeHtml(section.id)}">${escapeHtml(section.title)}</a>`).join('');
|
|
26
|
-
const sectionMarkup = sections.map((section) =>
|
|
172
|
+
const sectionMarkup = sections.map((section) => {
|
|
173
|
+
const own = findingsBySection.get(section.id) || [];
|
|
174
|
+
const findingsBlock = own.length > 0
|
|
175
|
+
? `<div class="findings"><div class="findings-title">${escapeHtml(labels.findings_title)} (${own.length})</div>${own.map((f) => renderFinding(f, labels)).join('\n')}</div>`
|
|
176
|
+
: '';
|
|
177
|
+
return `
|
|
27
178
|
<section class="section" id="${escapeHtml(section.id)}" data-section="${escapeHtml(section.id)}">
|
|
28
179
|
<div class="section-head">
|
|
29
180
|
<h2>${escapeHtml(section.title)}</h2>
|
|
181
|
+
<span class="chip chip-state" data-role="state-chip"></span>
|
|
30
182
|
<select data-role="status">
|
|
31
183
|
<option value="unchanged">unchanged</option>
|
|
32
184
|
<option value="accepted">accepted</option>
|
|
@@ -36,16 +188,49 @@ function buildReviewHtml(data) {
|
|
|
36
188
|
</select>
|
|
37
189
|
</div>
|
|
38
190
|
<div class="editor" contenteditable="plaintext-only" spellcheck="true">${escapeHtml(section.current_text || '')}</div>
|
|
39
|
-
<label class="note-label"
|
|
40
|
-
<textarea class="note" data-role="note" rows="3" placeholder="
|
|
41
|
-
|
|
191
|
+
<label class="note-label">${escapeHtml(labels.notes)}</label>
|
|
192
|
+
<textarea class="note" data-role="note" rows="3" placeholder="${escapeHtml(labels.note_placeholder)}"></textarea>
|
|
193
|
+
${findingsBlock}
|
|
194
|
+
</section>`;
|
|
195
|
+
}).join('\n');
|
|
196
|
+
|
|
197
|
+
const orphanMarkup = orphanFindings.length > 0
|
|
198
|
+
? `
|
|
199
|
+
<section class="section" data-section="">
|
|
200
|
+
<div class="section-head"><h2>${escapeHtml(labels.other_findings)}</h2></div>
|
|
201
|
+
<div class="findings">${orphanFindings.map((f) => renderFinding(f, labels)).join('\n')}</div>
|
|
202
|
+
</section>`
|
|
203
|
+
: '';
|
|
204
|
+
|
|
205
|
+
const filterButtons = [`<button type="button" data-cat="all" class="active">${escapeHtml(labels.filter_all)}</button>`]
|
|
206
|
+
.concat(FINDING_CATEGORIES.map((cat) => `<button type="button" data-cat="${cat}">${cat}</button>`))
|
|
207
|
+
.join('');
|
|
208
|
+
|
|
209
|
+
const hintBody = labels.hint_body
|
|
210
|
+
.map((line) => escapeHtml(line).split(escapeHtml('{feedbackPath}')).join(`<code>${escapeHtml(feedbackPath)}</code>`))
|
|
211
|
+
.map((line) => `<p>${line}</p>`)
|
|
212
|
+
.join('');
|
|
213
|
+
|
|
214
|
+
const runtimeLabels = {
|
|
215
|
+
section_blocked: labels.section_blocked,
|
|
216
|
+
blocks_prd: labels.blocks_prd,
|
|
217
|
+
autosaved: labels.autosaved,
|
|
218
|
+
draft_restored: labels.draft_restored,
|
|
219
|
+
downloaded: labels.downloaded,
|
|
220
|
+
copied: labels.copied,
|
|
221
|
+
saved: labels.saved,
|
|
222
|
+
path_copied: labels.path_copied,
|
|
223
|
+
no_fsa: labels.no_fsa,
|
|
224
|
+
sandbox_fallback: labels.sandbox_fallback
|
|
225
|
+
};
|
|
42
226
|
|
|
43
227
|
return `<!doctype html>
|
|
44
|
-
|
|
228
|
+
<!-- aioson:review schema=${escapeHtml(feedback.schema_version)} slug=${escapeHtml(feedback.briefing_slug)} source_hash=${escapeHtml(feedback.source_hash)} -->
|
|
229
|
+
<html lang="${labels === LABELS.pt ? 'pt-BR' : 'en'}">
|
|
45
230
|
<head>
|
|
46
231
|
<meta charset="utf-8">
|
|
47
232
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
48
|
-
<title
|
|
233
|
+
<title>${escapeHtml(labels.title)} - ${escapeHtml(feedback.briefing_slug)}</title>
|
|
49
234
|
<style>
|
|
50
235
|
:root { color-scheme: light; --bg: #f6f7f9; --panel: #ffffff; --ink: #172026; --muted: #5c6975; --line: #d8dee6; --accent: #0b6f85; --block: #b42318; --change: #8a5a00; --ok: #1b7f3a; }
|
|
51
236
|
* { box-sizing: border-box; }
|
|
@@ -54,111 +239,205 @@ function buildReviewHtml(data) {
|
|
|
54
239
|
h1 { margin: 0 0 4px; font-size: 20px; }
|
|
55
240
|
h2 { margin: 0; font-size: 16px; }
|
|
56
241
|
.meta { color: var(--muted); font-size: 12px; }
|
|
242
|
+
.banner { margin-top: 8px; padding: 6px 10px; border: 1px solid var(--line); border-radius: 6px; background: #fff8e6; font-size: 12px; display: flex; gap: 10px; align-items: center; }
|
|
243
|
+
.banner button { min-height: 24px; padding: 2px 8px; font-size: 12px; }
|
|
57
244
|
.layout { display: grid; grid-template-columns: 220px minmax(0, 1fr) 300px; gap: 16px; padding: 16px; }
|
|
58
245
|
nav, aside { position: sticky; top: 82px; align-self: start; max-height: calc(100vh - 100px); overflow: auto; }
|
|
59
246
|
nav a { display: block; padding: 8px 10px; color: var(--ink); text-decoration: none; border-left: 3px solid transparent; }
|
|
60
247
|
nav a:hover { border-left-color: var(--accent); background: #eaf3f6; }
|
|
61
248
|
.toolbar, nav, aside, .section { background: var(--panel); border: 1px solid var(--line); border-radius: 6px; }
|
|
62
249
|
.toolbar { padding: 10px; margin-bottom: 12px; display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
|
|
63
|
-
|
|
250
|
+
.toolbar .target { flex-basis: 100%; display: flex; gap: 8px; align-items: center; min-width: 0; }
|
|
251
|
+
.toolbar .target code { font-size: 12px; background: var(--bg); border: 1px solid var(--line); border-radius: 4px; padding: 3px 6px; overflow-x: auto; white-space: nowrap; }
|
|
252
|
+
.toolbar .target button { min-height: 26px; padding: 2px 8px; font-size: 12px; flex-shrink: 0; }
|
|
253
|
+
button, select, input.f-note { border: 1px solid var(--line); background: #fff; border-radius: 4px; padding: 5px 8px; color: var(--ink); font: inherit; }
|
|
254
|
+
button, select { min-height: 32px; }
|
|
255
|
+
button { cursor: pointer; }
|
|
64
256
|
button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
65
257
|
main { min-width: 0; }
|
|
66
258
|
.section { margin-bottom: 12px; overflow: hidden; }
|
|
67
|
-
.section-head { display: flex;
|
|
259
|
+
.section-head { display: flex; align-items: center; gap: 12px; padding: 10px 12px; border-bottom: 1px solid var(--line); background: #fbfcfd; }
|
|
260
|
+
.section-head h2 { flex: 1; }
|
|
261
|
+
.section.is-blocked .section-head { background: #fdf0ee; }
|
|
68
262
|
.editor { min-height: 96px; padding: 12px; white-space: pre-wrap; outline: none; }
|
|
69
263
|
.editor:focus { box-shadow: inset 0 0 0 2px var(--accent); }
|
|
70
264
|
.note-label { display: block; padding: 0 12px 4px; color: var(--muted); font-size: 12px; }
|
|
71
265
|
.note { width: calc(100% - 24px); margin: 0 12px 12px; resize: vertical; border: 1px solid var(--line); border-radius: 4px; padding: 8px; font: inherit; }
|
|
266
|
+
.findings { border-top: 1px dashed var(--line); padding: 10px 12px 12px; }
|
|
267
|
+
.findings-title { color: var(--muted); font-size: 12px; margin-bottom: 8px; }
|
|
268
|
+
.finding { border: 1px solid var(--line); border-radius: 6px; padding: 8px 10px; margin-bottom: 8px; background: #fbfcfd; }
|
|
269
|
+
.finding[hidden] { display: none; }
|
|
270
|
+
.f-head { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; flex-wrap: wrap; }
|
|
271
|
+
.f-head select { margin-left: auto; min-height: 26px; padding: 2px 6px; font-size: 12px; }
|
|
272
|
+
.f-id { color: var(--muted); font-size: 11px; }
|
|
273
|
+
.f-text { margin-bottom: 6px; }
|
|
274
|
+
.f-rec { color: var(--muted); font-size: 13px; margin-bottom: 6px; }
|
|
275
|
+
.f-note { width: 100%; font-size: 13px; }
|
|
276
|
+
.chip { display: inline-block; border-radius: 999px; padding: 1px 8px; font-size: 11px; border: 1px solid var(--line); background: #fff; }
|
|
277
|
+
.chip-cat { border-color: var(--accent); color: var(--accent); }
|
|
278
|
+
.chip-sev-high { border-color: var(--block); color: var(--block); }
|
|
279
|
+
.chip-sev-medium { border-color: var(--change); color: var(--change); }
|
|
280
|
+
.chip-sev-low { color: var(--muted); }
|
|
281
|
+
.chip-block { background: var(--block); border-color: var(--block); color: #fff; }
|
|
282
|
+
.chip-state { display: none; font-size: 11px; }
|
|
283
|
+
.section.is-changed .chip-state { display: inline-block; border-color: var(--change); color: var(--change); }
|
|
284
|
+
.section.is-blocked .chip-state { display: inline-block; border-color: var(--block); color: var(--block); }
|
|
72
285
|
aside { padding: 12px; }
|
|
73
286
|
.summary-item { display: flex; justify-content: space-between; border-bottom: 1px solid var(--line); padding: 8px 0; }
|
|
74
|
-
.filters
|
|
287
|
+
.filters { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
|
|
288
|
+
.filters button { border-radius: 999px; font-size: 12px; min-height: 26px; padding: 2px 10px; }
|
|
289
|
+
.filters button.active { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
75
290
|
.danger { color: var(--block); font-weight: 600; }
|
|
76
|
-
.status { margin-top: 10px; color: var(--muted); min-height: 20px; }
|
|
291
|
+
.status { margin-top: 10px; color: var(--muted); min-height: 20px; font-size: 12px; }
|
|
292
|
+
.autosave { color: var(--muted); font-size: 11px; min-height: 16px; }
|
|
293
|
+
.hint { margin-top: 12px; color: var(--muted); font-size: 12px; border-top: 1px solid var(--line); padding-top: 10px; }
|
|
294
|
+
.hint p { margin: 0 0 6px; }
|
|
295
|
+
#blockers { white-space: pre-wrap; color: var(--block); font-size: 12px; margin-top: 8px; }
|
|
77
296
|
@media (max-width: 900px) { .layout { grid-template-columns: 1fr; } nav, aside { position: static; max-height: none; } }
|
|
78
297
|
</style>
|
|
79
298
|
</head>
|
|
80
299
|
<body>
|
|
81
300
|
<header>
|
|
82
|
-
<h1
|
|
83
|
-
<div class="meta">${escapeHtml(feedback.source_briefing_path)} | source hash ${escapeHtml(feedback.source_hash)}</div>
|
|
301
|
+
<h1>${escapeHtml(labels.title)}: ${escapeHtml(feedback.briefing_slug)}</h1>
|
|
302
|
+
<div class="meta">${escapeHtml(labels.source)}: ${escapeHtml(feedback.source_briefing_path)} | source hash ${escapeHtml(feedback.source_hash)} | ${escapeHtml(labels.round)} ${escapeHtml(String(feedback.round || 1))}</div>
|
|
303
|
+
<div class="banner" id="restore-banner" hidden><span></span><button type="button" id="discard-draft">${escapeHtml(labels.discard_draft)}</button></div>
|
|
84
304
|
</header>
|
|
85
305
|
<div class="layout">
|
|
86
306
|
<nav aria-label="Sections">${nav}</nav>
|
|
87
307
|
<main>
|
|
88
308
|
<div class="toolbar">
|
|
89
|
-
<button class="primary" id="download"
|
|
90
|
-
<button id="copy"
|
|
91
|
-
<button id="save"
|
|
92
|
-
<span class="meta"
|
|
309
|
+
<button type="button" class="primary" id="download">${escapeHtml(labels.btn_download)}</button>
|
|
310
|
+
<button type="button" id="copy">${escapeHtml(labels.btn_copy)}</button>
|
|
311
|
+
<button type="button" id="save">${escapeHtml(labels.btn_save)}</button>
|
|
312
|
+
<span class="meta">${escapeHtml(labels.toolbar_hint)}</span>${feedbackAbsPath ? `
|
|
313
|
+
<div class="target"><span class="meta">${escapeHtml(labels.target_hint)}</span> <code id="target-path">${escapeHtml(feedbackAbsPath)}</code> <button type="button" id="copy-path">${escapeHtml(labels.btn_copy_path)}</button></div>` : ''}
|
|
93
314
|
</div>
|
|
94
315
|
${sectionMarkup}
|
|
316
|
+
${orphanMarkup}
|
|
95
317
|
</main>
|
|
96
318
|
<aside>
|
|
97
|
-
<h2
|
|
98
|
-
<div class="summary-item"><span
|
|
99
|
-
<div class="summary-item"><span
|
|
100
|
-
<div class="summary-item"><span
|
|
101
|
-
<
|
|
102
|
-
<div
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
<label><input type="checkbox" value="gap"> gap</label>
|
|
106
|
-
<label><input type="checkbox" value="risk"> risk</label>
|
|
107
|
-
<label><input type="checkbox" value="pending-decision"> pending decision</label>
|
|
108
|
-
<label><input type="checkbox" value="scope"> scope suggestion</label>
|
|
109
|
-
</div>
|
|
319
|
+
<h2>${escapeHtml(labels.summary_title)}</h2>
|
|
320
|
+
<div class="summary-item"><span>${escapeHtml(labels.summary_changed)}</span><strong id="changed">0</strong></div>
|
|
321
|
+
<div class="summary-item"><span>${escapeHtml(labels.summary_blocked)}</span><strong id="blocked" class="danger">0</strong></div>
|
|
322
|
+
<div class="summary-item"><span>${escapeHtml(labels.summary_notes)}</span><strong id="notes">0</strong></div>
|
|
323
|
+
<div class="summary-item"><span>${escapeHtml(labels.summary_pending_findings)}</span><strong id="pending-findings">0</strong></div>
|
|
324
|
+
<div id="blockers"></div>
|
|
325
|
+
<h2 style="margin-top:16px">${escapeHtml(labels.filters_title)}</h2>
|
|
326
|
+
<div class="filters" id="filters">${filterButtons}</div>
|
|
110
327
|
<div class="status" id="status"></div>
|
|
328
|
+
<div class="autosave" id="autosave"></div>
|
|
329
|
+
<div class="hint"><b>${escapeHtml(labels.hint_title)}:</b>${hintBody}<b>${escapeHtml(labels.blocks_title)}:</b> ${escapeHtml(labels.blocks_body)}</div>
|
|
111
330
|
</aside>
|
|
112
331
|
</div>
|
|
113
332
|
<script>
|
|
114
333
|
const feedback = ${safeJson(feedback)};
|
|
115
|
-
const
|
|
334
|
+
const L = ${safeJson(runtimeLabels)};
|
|
335
|
+
const LS_KEY = 'aioson-review:' + feedback.briefing_slug + ':' + feedback.source_hash;
|
|
336
|
+
const sectionById = new Map(feedback.sections.map(section => [section.id, section]));
|
|
337
|
+
const findingById = new Map((feedback.findings || []).map(finding => [finding.id, finding]));
|
|
116
338
|
const statusEl = document.getElementById('status');
|
|
339
|
+
const autosaveEl = document.getElementById('autosave');
|
|
117
340
|
|
|
118
341
|
function plainText(node) {
|
|
119
|
-
return (node.innerText || node.textContent || '').replace(/\\r\\n
|
|
342
|
+
return (node.innerText || node.textContent || '').replace(/\\r\\n?/g, '\\n');
|
|
120
343
|
}
|
|
121
344
|
|
|
122
|
-
function
|
|
345
|
+
function setText(id, value) { document.getElementById(id).textContent = String(value); }
|
|
346
|
+
|
|
347
|
+
function collect() {
|
|
123
348
|
feedback.last_modified_at = new Date().toISOString();
|
|
124
349
|
let changed = 0;
|
|
125
|
-
let blocked = 0;
|
|
126
350
|
let notes = 0;
|
|
127
|
-
document.querySelectorAll('.section').forEach(sectionEl => {
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
351
|
+
document.querySelectorAll('.section[data-section]').forEach(sectionEl => {
|
|
352
|
+
const section = sectionById.get(sectionEl.dataset.section);
|
|
353
|
+
if (!section) return;
|
|
354
|
+
section.current_text = plainText(sectionEl.querySelector('.editor'));
|
|
355
|
+
section.status = sectionEl.querySelector('[data-role="status"]').value;
|
|
132
356
|
const note = sectionEl.querySelector('[data-role="note"]').value.trim();
|
|
133
|
-
section.current_text = text;
|
|
134
|
-
section.status = status;
|
|
135
357
|
section.comments_count = note ? 1 : 0;
|
|
136
|
-
|
|
137
|
-
if (
|
|
358
|
+
const isChanged = section.current_text !== section.original_text || section.status !== 'unchanged';
|
|
359
|
+
if (isChanged) changed += 1;
|
|
138
360
|
if (note) notes += 1;
|
|
361
|
+
sectionEl.classList.toggle('is-changed', isChanged);
|
|
362
|
+
sectionEl.classList.toggle('is-blocked', section.status === 'blocked');
|
|
363
|
+
const chip = sectionEl.querySelector('[data-role="state-chip"]');
|
|
364
|
+
if (chip) chip.textContent = section.status === 'blocked' ? 'blocked' : (isChanged ? section.status : '');
|
|
365
|
+
});
|
|
366
|
+
document.querySelectorAll('.finding').forEach(findingEl => {
|
|
367
|
+
const finding = findingById.get(findingEl.dataset.finding);
|
|
368
|
+
if (!finding) return;
|
|
369
|
+
finding.status = findingEl.querySelector('[data-role="f-status"]').value;
|
|
370
|
+
finding.note = findingEl.querySelector('[data-role="f-note"]').value.trim();
|
|
139
371
|
});
|
|
140
372
|
feedback.comments = [];
|
|
141
373
|
feedback.blocking_items = [];
|
|
142
|
-
document.querySelectorAll('.section').forEach(sectionEl => {
|
|
143
|
-
const
|
|
374
|
+
document.querySelectorAll('.section[data-section]').forEach(sectionEl => {
|
|
375
|
+
const section = sectionById.get(sectionEl.dataset.section);
|
|
376
|
+
if (!section) return;
|
|
144
377
|
const note = sectionEl.querySelector('[data-role="note"]').value.trim();
|
|
145
|
-
const status = sectionEl.querySelector('[data-role="status"]').value;
|
|
146
378
|
if (note) {
|
|
147
|
-
const severity = status === 'blocked' ? 'blocking' : 'note';
|
|
148
|
-
feedback.comments.push({ id: 'comment-' + id, section_id: id, target_text_hash: null, note, severity, resolved: false });
|
|
379
|
+
const severity = section.status === 'blocked' ? 'blocking' : 'note';
|
|
380
|
+
feedback.comments.push({ id: 'comment-' + section.id, section_id: section.id, target_text_hash: null, note, severity, resolved: false });
|
|
381
|
+
}
|
|
382
|
+
if (section.status === 'blocked') {
|
|
383
|
+
feedback.blocking_items.push({ id: 'block-' + section.id, section_id: section.id, note: note || L.section_blocked, resolved: false });
|
|
149
384
|
}
|
|
150
|
-
|
|
151
|
-
|
|
385
|
+
});
|
|
386
|
+
(feedback.findings || []).forEach(finding => {
|
|
387
|
+
if (finding.blocking && finding.status === 'pending') {
|
|
388
|
+
feedback.blocking_items.push({ id: 'block-' + finding.id, section_id: finding.section_id, note: finding.text, resolved: false });
|
|
152
389
|
}
|
|
153
390
|
});
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
391
|
+
const pendingFindings = (feedback.findings || []).filter(finding => finding.status === 'pending').length;
|
|
392
|
+
setText('changed', changed);
|
|
393
|
+
setText('blocked', feedback.blocking_items.length);
|
|
394
|
+
setText('notes', notes);
|
|
395
|
+
setText('pending-findings', pendingFindings);
|
|
396
|
+
const blockersEl = document.getElementById('blockers');
|
|
397
|
+
blockersEl.textContent = feedback.blocking_items.length
|
|
398
|
+
? L.blocks_prd + '\\n' + feedback.blocking_items.map(item => '- ' + item.note).join('\\n')
|
|
399
|
+
: '';
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function autosave() {
|
|
403
|
+
try {
|
|
404
|
+
localStorage.setItem(LS_KEY, JSON.stringify(feedback));
|
|
405
|
+
autosaveEl.textContent = L.autosaved + ' ' + new Date().toTimeString().slice(0, 5);
|
|
406
|
+
} catch (error) { /* storage unavailable (some sandboxes) — export still works */ }
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function touch() { collect(); autosave(); }
|
|
410
|
+
|
|
411
|
+
function restoreDraft() {
|
|
412
|
+
let raw = null;
|
|
413
|
+
try { raw = localStorage.getItem(LS_KEY); } catch (error) { return; }
|
|
414
|
+
if (!raw) return;
|
|
415
|
+
let draft;
|
|
416
|
+
try { draft = JSON.parse(raw); } catch (error) { return; }
|
|
417
|
+
if (!draft || draft.source_hash !== feedback.source_hash) return;
|
|
418
|
+
(draft.sections || []).forEach(ds => {
|
|
419
|
+
const sectionEl = document.querySelector('.section[data-section="' + ds.id + '"]');
|
|
420
|
+
if (!sectionEl) return;
|
|
421
|
+
if (typeof ds.current_text === 'string') sectionEl.querySelector('.editor').innerText = ds.current_text;
|
|
422
|
+
sectionEl.querySelector('[data-role="status"]').value = ds.status || 'unchanged';
|
|
423
|
+
const draftNote = (draft.comments || []).find(comment => comment.section_id === ds.id);
|
|
424
|
+
sectionEl.querySelector('[data-role="note"]').value = draftNote ? draftNote.note : '';
|
|
425
|
+
});
|
|
426
|
+
(draft.findings || []).forEach(df => {
|
|
427
|
+
const findingEl = document.querySelector('.finding[data-finding="' + df.id + '"]');
|
|
428
|
+
if (!findingEl) return;
|
|
429
|
+
findingEl.querySelector('[data-role="f-status"]').value = df.status || 'pending';
|
|
430
|
+
findingEl.querySelector('[data-role="f-note"]').value = df.note || '';
|
|
431
|
+
});
|
|
432
|
+
const banner = document.getElementById('restore-banner');
|
|
433
|
+
banner.hidden = false;
|
|
434
|
+
banner.querySelector('span').textContent = L.draft_restored
|
|
435
|
+
+ (draft.last_modified_at ? ' (' + draft.last_modified_at.slice(0, 16).replace('T', ' ') + ')' : '');
|
|
157
436
|
}
|
|
158
437
|
|
|
159
438
|
function jsonText(method) {
|
|
160
439
|
feedback.export_method = method;
|
|
161
|
-
|
|
440
|
+
collect();
|
|
162
441
|
return JSON.stringify(feedback, null, 2);
|
|
163
442
|
}
|
|
164
443
|
|
|
@@ -169,54 +448,95 @@ function buildReviewHtml(data) {
|
|
|
169
448
|
link.download = 'refinement-feedback.json';
|
|
170
449
|
link.click();
|
|
171
450
|
URL.revokeObjectURL(link.href);
|
|
172
|
-
statusEl.textContent =
|
|
451
|
+
statusEl.textContent = L.downloaded;
|
|
452
|
+
autosave();
|
|
173
453
|
}
|
|
174
454
|
|
|
175
455
|
async function copyJson() {
|
|
176
456
|
await navigator.clipboard.writeText(jsonText('copy-paste'));
|
|
177
|
-
statusEl.textContent =
|
|
457
|
+
statusEl.textContent = L.copied;
|
|
458
|
+
autosave();
|
|
178
459
|
}
|
|
179
460
|
|
|
180
|
-
|
|
461
|
+
let fileHandle = null;
|
|
462
|
+
async function saveDirect() {
|
|
181
463
|
if (!window.showSaveFilePicker) {
|
|
182
464
|
download();
|
|
183
|
-
statusEl.textContent =
|
|
465
|
+
statusEl.textContent = L.no_fsa;
|
|
184
466
|
return;
|
|
185
467
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
468
|
+
try {
|
|
469
|
+
if (!fileHandle) {
|
|
470
|
+
// id: Chromium remembers the last-used directory per picker id, so after the
|
|
471
|
+
// first save the dialog reopens in the briefing folder on later rounds.
|
|
472
|
+
fileHandle = await window.showSaveFilePicker({ id: 'aioson-refinement-feedback', suggestedName: 'refinement-feedback.json', types: [{ description: 'JSON', accept: { 'application/json': ['.json'] } }] });
|
|
473
|
+
}
|
|
474
|
+
const writable = await fileHandle.createWritable();
|
|
475
|
+
await writable.write(jsonText('file-system-access'));
|
|
476
|
+
await writable.close();
|
|
477
|
+
statusEl.textContent = L.saved;
|
|
478
|
+
autosave();
|
|
479
|
+
} catch (error) {
|
|
480
|
+
if (error && error.name === 'AbortError') return; // user cancelled the picker
|
|
481
|
+
// SecurityError/NotAllowedError: sandboxed preview (or revoked handle) —
|
|
482
|
+
// degrade to download instead of dead-ending with a raw error message.
|
|
483
|
+
fileHandle = null;
|
|
484
|
+
download();
|
|
485
|
+
statusEl.textContent = L.sandbox_fallback;
|
|
486
|
+
}
|
|
191
487
|
}
|
|
192
488
|
|
|
193
489
|
document.addEventListener('input', touch);
|
|
194
490
|
document.addEventListener('change', touch);
|
|
195
491
|
document.getElementById('download').addEventListener('click', download);
|
|
196
492
|
document.getElementById('copy').addEventListener('click', () => copyJson().catch(() => download()));
|
|
197
|
-
document.getElementById('save').addEventListener('click',
|
|
493
|
+
document.getElementById('save').addEventListener('click', saveDirect);
|
|
494
|
+
const copyPathBtn = document.getElementById('copy-path');
|
|
495
|
+
if (copyPathBtn) copyPathBtn.addEventListener('click', async () => {
|
|
496
|
+
try {
|
|
497
|
+
await navigator.clipboard.writeText(document.getElementById('target-path').textContent);
|
|
498
|
+
statusEl.textContent = L.path_copied;
|
|
499
|
+
} catch (error) { /* clipboard unavailable (sandboxed preview) — path stays visible for manual copy */ }
|
|
500
|
+
});
|
|
501
|
+
document.getElementById('discard-draft').addEventListener('click', () => {
|
|
502
|
+
try { localStorage.removeItem(LS_KEY); } catch (error) { /* ignore */ }
|
|
503
|
+
location.reload();
|
|
504
|
+
});
|
|
505
|
+
document.getElementById('filters').addEventListener('click', (event) => {
|
|
506
|
+
const btn = event.target.closest('button[data-cat]');
|
|
507
|
+
if (!btn) return;
|
|
508
|
+
document.querySelectorAll('#filters button').forEach(other => other.classList.remove('active'));
|
|
509
|
+
btn.classList.add('active');
|
|
510
|
+
const cat = btn.dataset.cat;
|
|
511
|
+
document.querySelectorAll('.finding').forEach(findingEl => {
|
|
512
|
+
findingEl.hidden = !(cat === 'all' || findingEl.dataset.cat === cat);
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
restoreDraft();
|
|
198
516
|
touch();
|
|
199
517
|
</script>
|
|
200
518
|
</body>
|
|
201
519
|
</html>`;
|
|
202
520
|
}
|
|
203
521
|
|
|
204
|
-
async function writeReviewArtifacts(projectDir, { slug, sourceMarkdown, sections, sourceHash }) {
|
|
522
|
+
async function writeReviewArtifacts(projectDir, { slug, sourceMarkdown, sections, sourceHash, findings = [], round = 1, locale = 'en' }) {
|
|
205
523
|
// Validates slug as a safe segment and asserts containment before any write.
|
|
206
524
|
const briefingDir = resolveBriefingPath(projectDir, slug);
|
|
207
525
|
const sourcePath = `.aioson/briefings/${slug}/briefings.md`;
|
|
208
|
-
const feedback = buildInitialFeedback({ slug, sourcePath, sourceHash, sections });
|
|
209
|
-
const html = buildReviewHtml({ feedback, sourceMarkdown });
|
|
526
|
+
const feedback = buildInitialFeedback({ slug, sourcePath, sourceHash, sections, findings, round });
|
|
527
|
+
const html = buildReviewHtml({ feedback, sourceMarkdown, locale, feedbackAbsPath: path.join(briefingDir, 'refinement-feedback.json') });
|
|
210
528
|
const report = buildRefinementReport({
|
|
211
529
|
briefing_slug: slug,
|
|
212
530
|
source_briefing_path: sourcePath,
|
|
213
531
|
source_hash: sourceHash,
|
|
214
532
|
status: 'review_generated',
|
|
215
|
-
next_action: '
|
|
533
|
+
next_action: 'collect_feedback',
|
|
534
|
+
round: feedback.round,
|
|
216
535
|
applied_changes: [],
|
|
217
536
|
skipped_changes: [],
|
|
218
537
|
unresolved_comments: [],
|
|
219
|
-
blocking_items: []
|
|
538
|
+
blocking_items: [],
|
|
539
|
+
findings: feedback.findings
|
|
220
540
|
});
|
|
221
541
|
|
|
222
542
|
await fs.mkdir(briefingDir, { recursive: true });
|
package/src/parser.js
CHANGED
|
@@ -30,6 +30,9 @@ function parseArgv(argv) {
|
|
|
30
30
|
'help', 'version', 'no-launch', 'attach', 'tmux',
|
|
31
31
|
'allow-warnings', 'install-hook', 'uninstall-hook', 'remove-hook',
|
|
32
32
|
'agent-safe', 'agentic',
|
|
33
|
+
// workflow:execute — pure booleans; `--seed .` / `--step .` must not
|
|
34
|
+
// swallow the path positional as their value.
|
|
35
|
+
'seed', 'seed-only', 'step',
|
|
33
36
|
'selective',
|
|
34
37
|
'status', 'suggest', 'apply',
|
|
35
38
|
'runtime-only', 'template-only', 'inception', 'locales',
|
|
@@ -44,6 +47,9 @@ function parseArgv(argv) {
|
|
|
44
47
|
// verify:artifact — pure booleans; `--advisory .` / `--no-build .` must
|
|
45
48
|
// not swallow the path positional.
|
|
46
49
|
'advisory', 'no-build',
|
|
50
|
+
// briefing:apply-feedback — pure booleans; `--confirm .` / `--declined .`
|
|
51
|
+
// / `--allow-stale .` must not swallow the path positional.
|
|
52
|
+
'confirm', 'declined', 'allow-stale',
|
|
47
53
|
// `--resume` alone means "resume last"; `--resume=<id>` carries a value
|
|
48
54
|
// and is handled by the `=` branch above. Without this entry, `--resume`
|
|
49
55
|
// followed by `--tool=claude` would swallow the next token as its value.
|