@jaimevalasek/aioson 1.18.0 → 1.20.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/package.json +1 -1
- package/src/cli.js +121 -9
- package/src/commands/feature-archive.js +165 -48
- package/src/constants.js +73 -0
- package/src/i18n/messages/en.js +24 -7
- package/src/i18n/messages/es.js +18 -1
- package/src/i18n/messages/fr.js +18 -1
- package/src/i18n/messages/pt-BR.js +24 -7
- package/template/.aioson/agents/dev.md +2 -0
- package/template/.aioson/agents/deyvin.md +1 -0
- package/template/.claude/commands/aioson/agent/analyst.md +16 -5
- package/template/.claude/commands/aioson/agent/architect.md +17 -5
- package/template/.claude/commands/aioson/agent/briefing.md +16 -5
- package/template/.claude/commands/aioson/agent/committer.md +16 -5
- package/template/.claude/commands/aioson/agent/copywriter.md +16 -5
- package/template/.claude/commands/aioson/agent/design-hybrid-forge.md +16 -5
- package/template/.claude/commands/aioson/agent/dev.md +18 -5
- package/template/.claude/commands/aioson/agent/deyvin.md +16 -5
- package/template/.claude/commands/aioson/agent/discover.md +16 -5
- package/template/.claude/commands/aioson/agent/discovery-design-doc.md +16 -5
- package/template/.claude/commands/aioson/agent/genome.md +16 -5
- package/template/.claude/commands/aioson/agent/neo.md +16 -5
- package/template/.claude/commands/aioson/agent/orache.md +16 -5
- package/template/.claude/commands/aioson/agent/orchestrator.md +21 -5
- package/template/.claude/commands/aioson/agent/pair.md +16 -5
- package/template/.claude/commands/aioson/agent/pentester.md +22 -5
- package/template/.claude/commands/aioson/agent/pm.md +20 -5
- package/template/.claude/commands/aioson/agent/product.md +16 -5
- package/template/.claude/commands/aioson/agent/profiler-enricher.md +16 -5
- package/template/.claude/commands/aioson/agent/profiler-forge.md +16 -5
- package/template/.claude/commands/aioson/agent/profiler-researcher.md +16 -5
- package/template/.claude/commands/aioson/agent/qa.md +16 -5
- package/template/.claude/commands/aioson/agent/setup.md +16 -5
- package/template/.claude/commands/aioson/agent/sheldon.md +16 -5
- package/template/.claude/commands/aioson/agent/site-forge.md +16 -5
- package/template/.claude/commands/aioson/agent/squad.md +16 -5
- package/template/.claude/commands/aioson/agent/tester.md +16 -5
- package/template/.claude/commands/aioson/agent/ux-ui.md +19 -5
- package/template/.claude/commands/aioson/agent/validator.md +17 -5
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -188,7 +188,7 @@ const { runOpShow } = require('./commands/op-show');
|
|
|
188
188
|
const { runOpReinforce } = require('./commands/op-reinforce');
|
|
189
189
|
const { runOpMigrate } = require('./commands/op-migrate');
|
|
190
190
|
const { runFeatureClose } = require('./commands/feature-close');
|
|
191
|
-
const { runFeatureArchive } = require('./commands/feature-archive');
|
|
191
|
+
const { runFeatureArchive, runFeatureSweep } = require('./commands/feature-archive');
|
|
192
192
|
const { runDossierInit, runDossierShow, runDossierAddFinding, runDossierAddCodemap, runDossierLinkRule, runDossierCompact } = require('./commands/dossier');
|
|
193
193
|
const { runDossierAddResearch } = require('./commands/dossier-add-research');
|
|
194
194
|
const { runDossierAudit } = require('./commands/dossier-audit');
|
|
@@ -229,6 +229,8 @@ const JSON_SUPPORTED_COMMANDS = new Set([
|
|
|
229
229
|
'agents',
|
|
230
230
|
'agent:prompt',
|
|
231
231
|
'agent-prompt',
|
|
232
|
+
'agent:help',
|
|
233
|
+
'agent-help',
|
|
232
234
|
'agent:invoke',
|
|
233
235
|
'agent-invoke',
|
|
234
236
|
'setup:context',
|
|
@@ -618,6 +620,8 @@ const JSON_SUPPORTED_COMMANDS = new Set([
|
|
|
618
620
|
'feature-close',
|
|
619
621
|
'feature:archive',
|
|
620
622
|
'feature-archive',
|
|
623
|
+
'feature:sweep',
|
|
624
|
+
'feature-sweep',
|
|
621
625
|
'dossier:init',
|
|
622
626
|
'dossier-init',
|
|
623
627
|
'dossier:show',
|
|
@@ -700,6 +704,10 @@ const JSON_SUPPORTED_COMMANDS = new Set([
|
|
|
700
704
|
'system-install'
|
|
701
705
|
]);
|
|
702
706
|
|
|
707
|
+
const AGENT_HELP_COMMANDS = new Set([
|
|
708
|
+
'agent:prompt', 'agent-prompt', 'agent:invoke', 'agent-invoke', 'agent:help', 'agent-help'
|
|
709
|
+
]);
|
|
710
|
+
|
|
703
711
|
const LEGACY_DASHBOARD_COMMANDS = new Set([
|
|
704
712
|
'dashboard:init',
|
|
705
713
|
'dashboard-init',
|
|
@@ -748,6 +756,7 @@ function printHelp(t, logger) {
|
|
|
748
756
|
logHelpLine(t, logger, 'cli.help_i18n_add');
|
|
749
757
|
logHelpLine(t, logger, 'cli.help_agents');
|
|
750
758
|
logHelpLine(t, logger, 'cli.help_agent_prompt');
|
|
759
|
+
logHelpLine(t, logger, 'cli.help_agent_help');
|
|
751
760
|
logHelpLine(t, logger, 'cli.help_agent_invoke');
|
|
752
761
|
logHelpLine(t, logger, 'cli.help_context_validate');
|
|
753
762
|
logHelpLine(t, logger, 'cli.help_context_pack');
|
|
@@ -851,6 +860,91 @@ function printHelp(t, logger) {
|
|
|
851
860
|
logHelpLine(t, logger, 'cli.help_squad_grant');
|
|
852
861
|
}
|
|
853
862
|
|
|
863
|
+
function printAgentHelp(agentName, jsonMode, logger, t) {
|
|
864
|
+
const { AGENT_DEFINITIONS } = require('./constants');
|
|
865
|
+
const { normalizeAgentName } = require('./agents');
|
|
866
|
+
|
|
867
|
+
if (!agentName) {
|
|
868
|
+
const list = AGENT_DEFINITIONS.map((a) => ({
|
|
869
|
+
id: a.id,
|
|
870
|
+
displayName: a.displayName,
|
|
871
|
+
description: a.description || ''
|
|
872
|
+
}));
|
|
873
|
+
if (jsonMode) {
|
|
874
|
+
writeJson({ ok: true, agents: list });
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
logger.log(`${t('agents.help_available')}\n`);
|
|
878
|
+
for (const a of list) {
|
|
879
|
+
logger.log(` @${a.id.padEnd(24)} ${a.description}`);
|
|
880
|
+
}
|
|
881
|
+
logger.log(`\n${t('agents.help_run_detail')}`);
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
const normalized = normalizeAgentName(agentName);
|
|
886
|
+
const agent = AGENT_DEFINITIONS.find((a) => {
|
|
887
|
+
if (a.id === normalized) return true;
|
|
888
|
+
return Array.isArray(a.aliases) && a.aliases.includes(normalized);
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
if (!agent) {
|
|
892
|
+
if (jsonMode) {
|
|
893
|
+
writeJson({ ok: false, error: t('agents.help_unknown_agent', { agent: agentName }) });
|
|
894
|
+
} else {
|
|
895
|
+
logger.error(t('agents.help_unknown_agent', { agent: agentName }));
|
|
896
|
+
}
|
|
897
|
+
process.exitCode = 1;
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
if (jsonMode) {
|
|
902
|
+
writeJson({
|
|
903
|
+
ok: true,
|
|
904
|
+
agent: {
|
|
905
|
+
id: agent.id,
|
|
906
|
+
displayName: agent.displayName,
|
|
907
|
+
description: agent.description || '',
|
|
908
|
+
command: agent.command,
|
|
909
|
+
path: agent.path,
|
|
910
|
+
aliases: agent.aliases || [],
|
|
911
|
+
flags: agent.flags || [],
|
|
912
|
+
dependsOn: agent.dependsOn,
|
|
913
|
+
output: agent.output
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
const aliasLine = agent.aliases && agent.aliases.length > 0
|
|
920
|
+
? ` (aliases: ${agent.aliases.join(', ')})`
|
|
921
|
+
: '';
|
|
922
|
+
logger.log(`${agent.command} — ${agent.description || agent.displayName}${aliasLine}\n`);
|
|
923
|
+
logger.log(`${t('agents.help_usage')}`);
|
|
924
|
+
logger.log(` aioson agent:prompt ${agent.id} [path] [options]`);
|
|
925
|
+
logger.log(` /${agent.id} [task description] ${t('agents.help_claude_code')}\n`);
|
|
926
|
+
logger.log(t('agents.help_common_options'));
|
|
927
|
+
logger.log(` --tool=<tool> ${t('agents.help_opt_tool')}`);
|
|
928
|
+
logger.log(` --language=<lang> ${t('agents.help_opt_language')}`);
|
|
929
|
+
logger.log(` --headless ${t('agents.help_opt_headless')}`);
|
|
930
|
+
logger.log(` --output=<path> ${t('agents.help_opt_output')}`);
|
|
931
|
+
logger.log(` --json ${t('agents.help_opt_json')}`);
|
|
932
|
+
if (agent.flags && agent.flags.length > 0) {
|
|
933
|
+
logger.log(`\n${t('agents.help_agent_options', { command: agent.command })}`);
|
|
934
|
+
for (const flag of agent.flags) {
|
|
935
|
+
logger.log(` --${flag.name}=${flag.value.padEnd(Math.max(12 - flag.name.length, 1))} ${flag.description}`);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
if (agent.dependsOn.length > 0) {
|
|
939
|
+
logger.log(`\n${t('agents.help_requires')}`);
|
|
940
|
+
for (const dep of agent.dependsOn) {
|
|
941
|
+
logger.log(` ${dep}`);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
logger.log(`\n${t('agents.help_produces')}\n ${agent.output}`);
|
|
945
|
+
logger.log(`\n${t('agents.help_instruction_file')}\n ${agent.path}`);
|
|
946
|
+
}
|
|
947
|
+
|
|
854
948
|
function commandSupportsJson(command) {
|
|
855
949
|
return JSON_SUPPORTED_COMMANDS.has(command);
|
|
856
950
|
}
|
|
@@ -868,19 +962,31 @@ async function main() {
|
|
|
868
962
|
const logger = createLogger();
|
|
869
963
|
const silentLogger = createSilentLogger();
|
|
870
964
|
|
|
871
|
-
if (command === 'help' || options.help || command === '--help' || command === '-h') {
|
|
872
|
-
printHelp(t, logger);
|
|
873
|
-
return;
|
|
874
|
-
}
|
|
875
|
-
|
|
876
965
|
if (command === '--version' || command === '-v' || command === 'version' || options.version) {
|
|
877
|
-
const
|
|
966
|
+
const { getCliVersionSync } = require('./version');
|
|
967
|
+
const version = getCliVersionSync();
|
|
878
968
|
if (jsonMode) {
|
|
879
|
-
writeJson(
|
|
969
|
+
writeJson({ ok: true, version });
|
|
970
|
+
} else {
|
|
971
|
+
logger.log(`aioson ${version}`);
|
|
880
972
|
}
|
|
881
973
|
return;
|
|
882
974
|
}
|
|
883
975
|
|
|
976
|
+
if (options.help && AGENT_HELP_COMMANDS.has(command)) {
|
|
977
|
+
printAgentHelp(args[0], jsonMode, logger, t);
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
if (command === 'agent:help' || command === 'agent-help') {
|
|
981
|
+
printAgentHelp(args[0], jsonMode, logger, t);
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
if (command === 'help' || options.help || command === '--help' || command === '-h') {
|
|
986
|
+
printHelp(t, logger);
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
|
|
884
990
|
if (LEGACY_DASHBOARD_COMMANDS.has(command)) {
|
|
885
991
|
const message = t('cli.dashboard_moved', { command });
|
|
886
992
|
if (jsonMode) {
|
|
@@ -1376,7 +1482,13 @@ async function main() {
|
|
|
1376
1482
|
} else if (command === 'feature:close' || command === 'feature-close') {
|
|
1377
1483
|
result = await runFeatureClose({ args, options, logger: commandLogger });
|
|
1378
1484
|
} else if (command === 'feature:archive' || command === 'feature-archive') {
|
|
1379
|
-
|
|
1485
|
+
if (options.sweep) {
|
|
1486
|
+
result = await runFeatureSweep({ args, options, logger: commandLogger });
|
|
1487
|
+
} else {
|
|
1488
|
+
result = await runFeatureArchive({ args, options, logger: commandLogger });
|
|
1489
|
+
}
|
|
1490
|
+
} else if (command === 'feature:sweep' || command === 'feature-sweep') {
|
|
1491
|
+
result = await runFeatureSweep({ args, options, logger: commandLogger });
|
|
1380
1492
|
} else if (command === 'dossier:init' || command === 'dossier-init') {
|
|
1381
1493
|
result = await runDossierInit({ args, options, logger: commandLogger });
|
|
1382
1494
|
} else if (command === 'dossier:show' || command === 'dossier-show') {
|
|
@@ -270,19 +270,42 @@ async function runFeatureArchive({ args = [], options = {}, logger }) {
|
|
|
270
270
|
const otherSlugs = await readOtherSlugs(featuresPath, slug);
|
|
271
271
|
const rootFiles = await findSlugFiles(ctxDir, slug, otherSlugs);
|
|
272
272
|
const alreadyArchived = (await dirExists(archiveDir)) ? await findArchivedFiles(archiveDir) : [];
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
273
|
+
|
|
274
|
+
const SLUG_DIRS = [
|
|
275
|
+
{ label: 'dossier', sourceBase: path.join(ctxDir, 'features'), archiveLabel: 'dossier' },
|
|
276
|
+
{ label: 'plans', sourceBase: path.join(targetDir, '.aioson', 'plans'), archiveLabel: 'plans' },
|
|
277
|
+
{ label: 'briefings', sourceBase: path.join(targetDir, '.aioson', 'briefings'), archiveLabel: 'briefings' }
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
const dirPlans = [];
|
|
281
|
+
for (const dir of SLUG_DIRS) {
|
|
282
|
+
const sourceDir = path.join(dir.sourceBase, slug);
|
|
283
|
+
const targetDirPath = path.join(archiveDir, dir.archiveLabel);
|
|
284
|
+
const hasSource = await dirExists(sourceDir);
|
|
285
|
+
const alreadyDone = await dirExists(targetDirPath);
|
|
286
|
+
if (hasSource || alreadyDone) {
|
|
287
|
+
dirPlans.push({
|
|
288
|
+
label: dir.label,
|
|
289
|
+
sourceDir,
|
|
290
|
+
targetDir: targetDirPath,
|
|
291
|
+
sourceBase: dir.sourceBase,
|
|
292
|
+
action: hasSource
|
|
293
|
+
? (alreadyDone ? 'skip' : 'move')
|
|
294
|
+
: (alreadyDone ? 'noop' : null),
|
|
295
|
+
reason: alreadyDone ? 'already_archived' : null
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const hasAnyDir = dirPlans.some((d) => d.action === 'move' || d.action === 'skip' || d.action === 'noop');
|
|
277
301
|
|
|
278
302
|
if (
|
|
279
303
|
rootFiles.length === 0 &&
|
|
280
304
|
alreadyArchived.length === 0 &&
|
|
281
|
-
!
|
|
282
|
-
!dossierAlreadyArchived
|
|
305
|
+
!hasAnyDir
|
|
283
306
|
) {
|
|
284
307
|
if (jsonOut) return { ok: true, slug, moved: [], skipped: [], alreadyArchived: [], noop: true };
|
|
285
|
-
log(`No files matched "*-${slug}.{${ARCHIVED_EXTENSIONS.join(',')}}" in .aioson/context/ root and no
|
|
308
|
+
log(`No files matched "*-${slug}.{${ARCHIVED_EXTENSIONS.join(',')}}" in .aioson/context/ root and no slug directories found — nothing to archive.`);
|
|
286
309
|
return { ok: true, noop: true };
|
|
287
310
|
}
|
|
288
311
|
|
|
@@ -305,11 +328,16 @@ async function runFeatureArchive({ args = [], options = {}, logger }) {
|
|
|
305
328
|
: null;
|
|
306
329
|
const summary = summarySource ? await extractSummary(summarySource) : null;
|
|
307
330
|
|
|
308
|
-
const dossierPlan = hasDossierToMove
|
|
309
|
-
? (dossierAlreadyArchived ? { action: 'skip', reason: 'already_archived' } : { action: 'move' })
|
|
310
|
-
: (dossierAlreadyArchived ? { action: 'noop', reason: 'already_archived' } : null);
|
|
311
|
-
|
|
312
331
|
if (dryRun) {
|
|
332
|
+
const dirs = dirPlans
|
|
333
|
+
.filter((d) => d.action)
|
|
334
|
+
.map((d) => ({
|
|
335
|
+
label: d.label,
|
|
336
|
+
source: path.relative(targetDir, d.sourceDir),
|
|
337
|
+
target: path.relative(targetDir, d.targetDir),
|
|
338
|
+
action: d.action,
|
|
339
|
+
reason: d.reason
|
|
340
|
+
}));
|
|
313
341
|
const result = {
|
|
314
342
|
ok: true,
|
|
315
343
|
dryRun: true,
|
|
@@ -317,13 +345,7 @@ async function runFeatureArchive({ args = [], options = {}, logger }) {
|
|
|
317
345
|
targetDir: path.relative(targetDir, archiveDir),
|
|
318
346
|
move: toMove,
|
|
319
347
|
skip: toSkip,
|
|
320
|
-
|
|
321
|
-
? {
|
|
322
|
-
source: path.relative(targetDir, dossierSourceDir),
|
|
323
|
-
target: path.relative(targetDir, dossierTargetDir),
|
|
324
|
-
...dossierPlan
|
|
325
|
-
}
|
|
326
|
-
: null,
|
|
348
|
+
dirs,
|
|
327
349
|
manifestEntry: {
|
|
328
350
|
slug,
|
|
329
351
|
completed,
|
|
@@ -340,10 +362,12 @@ async function runFeatureArchive({ args = [], options = {}, logger }) {
|
|
|
340
362
|
log(` would skip: ${toSkip.length} file(s)`);
|
|
341
363
|
for (const s of toSkip) log(` • ${s.name} (${s.reason})`);
|
|
342
364
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
365
|
+
for (const d of dirPlans) {
|
|
366
|
+
if (d.action === 'move') {
|
|
367
|
+
log(` would move ${d.label} dir: ${path.relative(targetDir, d.sourceDir)}/ → ${path.relative(targetDir, d.targetDir)}/`);
|
|
368
|
+
} else if (d.action === 'skip') {
|
|
369
|
+
log(` would skip ${d.label} dir: already archived at ${path.relative(targetDir, d.targetDir)}/`);
|
|
370
|
+
}
|
|
347
371
|
}
|
|
348
372
|
log(` manifest entry: | ${slug} | ${completed} | ${toMove.length + alreadyArchived.length} | ${summary || '—'} |`);
|
|
349
373
|
return result;
|
|
@@ -359,27 +383,29 @@ async function runFeatureArchive({ args = [], options = {}, logger }) {
|
|
|
359
383
|
moved.push(name);
|
|
360
384
|
}
|
|
361
385
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
386
|
+
const dirResults = [];
|
|
387
|
+
for (const d of dirPlans) {
|
|
388
|
+
if (d.action === 'move') {
|
|
389
|
+
await fs.mkdir(path.dirname(d.targetDir), { recursive: true });
|
|
390
|
+
await fs.rename(d.sourceDir, d.targetDir);
|
|
391
|
+
dirResults.push({
|
|
392
|
+
label: d.label,
|
|
393
|
+
action: 'moved',
|
|
394
|
+
source: path.relative(targetDir, d.sourceDir),
|
|
395
|
+
target: path.relative(targetDir, d.targetDir)
|
|
396
|
+
});
|
|
397
|
+
try {
|
|
398
|
+
const remaining = await fs.readdir(d.sourceBase);
|
|
399
|
+
if (remaining.length === 0) await fs.rmdir(d.sourceBase);
|
|
400
|
+
} catch { /* parent missing or non-empty */ }
|
|
401
|
+
} else if (d.action === 'skip') {
|
|
402
|
+
dirResults.push({
|
|
403
|
+
label: d.label,
|
|
404
|
+
action: 'skipped',
|
|
405
|
+
reason: d.reason,
|
|
406
|
+
target: path.relative(targetDir, d.targetDir)
|
|
407
|
+
});
|
|
376
408
|
}
|
|
377
|
-
} else if (dossierPlan && dossierPlan.action === 'skip') {
|
|
378
|
-
dossierResult = {
|
|
379
|
-
action: 'skipped',
|
|
380
|
-
reason: dossierPlan.reason,
|
|
381
|
-
target: path.relative(targetDir, dossierTargetDir)
|
|
382
|
-
};
|
|
383
409
|
}
|
|
384
410
|
|
|
385
411
|
const totalArchived = (await findArchivedFiles(archiveDir)).length;
|
|
@@ -399,7 +425,8 @@ async function runFeatureArchive({ args = [], options = {}, logger }) {
|
|
|
399
425
|
moved,
|
|
400
426
|
skipped: toSkip,
|
|
401
427
|
totalArchived,
|
|
402
|
-
|
|
428
|
+
dirs: dirResults.length > 0 ? dirResults : undefined,
|
|
429
|
+
dossier: dirResults.find((d) => d.label === 'dossier') || null,
|
|
403
430
|
manifestEntry: entry
|
|
404
431
|
};
|
|
405
432
|
|
|
@@ -412,10 +439,12 @@ async function runFeatureArchive({ args = [], options = {}, logger }) {
|
|
|
412
439
|
log(` skipped: ${toSkip.length} file(s) already in archive`);
|
|
413
440
|
for (const s of toSkip) log(` • ${s.name}`);
|
|
414
441
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
418
|
-
|
|
442
|
+
for (const d of dirResults) {
|
|
443
|
+
if (d.action === 'moved') {
|
|
444
|
+
log(` moved ${d.label} dir: ${d.source}/ → ${d.target}/`);
|
|
445
|
+
} else if (d.action === 'skipped') {
|
|
446
|
+
log(` skipped ${d.label} dir: already archived at ${d.target}/`);
|
|
447
|
+
}
|
|
419
448
|
}
|
|
420
449
|
log(` manifest updated: .aioson/context/done/MANIFEST.md`);
|
|
421
450
|
return result;
|
|
@@ -510,4 +539,92 @@ async function runRestore({ slug, ctxDir, archiveDir, manifestPath, dryRun, json
|
|
|
510
539
|
return result;
|
|
511
540
|
}
|
|
512
541
|
|
|
513
|
-
|
|
542
|
+
async function listDoneFeatures(featuresPath) {
|
|
543
|
+
const content = await readFileSafe(featuresPath);
|
|
544
|
+
if (!content) return [];
|
|
545
|
+
const results = [];
|
|
546
|
+
const lines = content.split(/\r?\n/);
|
|
547
|
+
for (const line of lines) {
|
|
548
|
+
const m = line.match(/^\|\s*([a-z][a-z0-9-]*)\s*\|\s*done\s*\|/i);
|
|
549
|
+
if (m) results.push(m[1].toLowerCase());
|
|
550
|
+
}
|
|
551
|
+
return results;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
async function listArchivedSlugs(manifestPath) {
|
|
555
|
+
const content = await readFileSafe(manifestPath);
|
|
556
|
+
if (!content) return new Set();
|
|
557
|
+
const slugs = new Set();
|
|
558
|
+
const lines = content.split(/\r?\n/);
|
|
559
|
+
for (const line of lines) {
|
|
560
|
+
const m = line.match(/^\|\s*([a-z][a-z0-9-]+)\s*\|/i);
|
|
561
|
+
if (m && m[1] !== 'slug') slugs.add(m[1].toLowerCase());
|
|
562
|
+
}
|
|
563
|
+
return slugs;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
async function runFeatureSweep({ args = [], options = {}, logger }) {
|
|
567
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
568
|
+
const dryRun = Boolean(options['dry-run'] || options.dryRun);
|
|
569
|
+
const jsonOut = Boolean(options.json);
|
|
570
|
+
const log = (msg) => { if (logger && !jsonOut) logger.log(msg); };
|
|
571
|
+
|
|
572
|
+
const ctxDir = contextDir(targetDir);
|
|
573
|
+
if (!(await dirExists(ctxDir))) {
|
|
574
|
+
if (jsonOut) return { ok: false, reason: 'no_context_dir' };
|
|
575
|
+
log('.aioson/context/ not found. Run aioson setup first.');
|
|
576
|
+
return { ok: false };
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const featuresPath = path.join(ctxDir, 'features.md');
|
|
580
|
+
const manifestPath = path.join(ctxDir, 'done', 'MANIFEST.md');
|
|
581
|
+
|
|
582
|
+
const doneSlugs = await listDoneFeatures(featuresPath);
|
|
583
|
+
const archivedSlugs = await listArchivedSlugs(manifestPath);
|
|
584
|
+
const pending = doneSlugs.filter((s) => !archivedSlugs.has(s));
|
|
585
|
+
|
|
586
|
+
if (pending.length === 0) {
|
|
587
|
+
const result = { ok: true, pending: [], archived: [] };
|
|
588
|
+
if (jsonOut) return result;
|
|
589
|
+
log('All done features are already archived.');
|
|
590
|
+
return result;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (dryRun) {
|
|
594
|
+
const result = { ok: true, dryRun: true, pending, archived: [] };
|
|
595
|
+
if (jsonOut) return result;
|
|
596
|
+
log(`[dry-run] ${pending.length} done feature(s) not yet archived:`);
|
|
597
|
+
for (const s of pending) log(` • ${s}`);
|
|
598
|
+
return result;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const archived = [];
|
|
602
|
+
const failed = [];
|
|
603
|
+
for (const slug of pending) {
|
|
604
|
+
try {
|
|
605
|
+
const archiveResult = await runFeatureArchive({
|
|
606
|
+
args: [targetDir],
|
|
607
|
+
options: { feature: slug, json: true },
|
|
608
|
+
logger: null
|
|
609
|
+
});
|
|
610
|
+
if (archiveResult && archiveResult.ok) {
|
|
611
|
+
const movedCount = archiveResult.moved ? archiveResult.moved.length : 0;
|
|
612
|
+
archived.push({ slug, moved: movedCount });
|
|
613
|
+
log(` ✓ ${slug} — ${movedCount} file(s) archived`);
|
|
614
|
+
} else {
|
|
615
|
+
failed.push({ slug, reason: archiveResult.reason || 'unknown' });
|
|
616
|
+
log(` ✗ ${slug} — ${archiveResult.reason || 'unknown'}`);
|
|
617
|
+
}
|
|
618
|
+
} catch (err) {
|
|
619
|
+
failed.push({ slug, reason: err.message || String(err) });
|
|
620
|
+
log(` ✗ ${slug} — ${err.message || err}`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const result = { ok: true, pending, archived, failed: failed.length > 0 ? failed : undefined };
|
|
625
|
+
if (jsonOut) return result;
|
|
626
|
+
log(`\nSweep complete: ${archived.length} archived, ${failed.length} failed.`);
|
|
627
|
+
return result;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
module.exports = { runFeatureArchive, runFeatureSweep };
|