@jaimevalasek/aioson 1.36.0 → 1.37.1
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 +42 -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/agent-execution/adapters/base.js +15 -0
- package/src/agent-execution/adapters/claude.js +3 -0
- package/src/agent-execution/adapters/codex.js +3 -0
- package/src/agent-execution/adapters/opencode.js +3 -0
- package/src/agent-execution/capabilities.js +9 -0
- package/src/agent-execution/dispatcher.js +72 -0
- package/src/agent-execution/executable-resolver.js +7 -0
- package/src/agent-execution/manifest.js +62 -0
- package/src/agent-execution/model-catalog.js +80 -0
- package/src/agent-execution/model-resolver.js +132 -0
- package/src/agent-execution/reports.js +9 -0
- package/src/agent-execution/schema.js +48 -0
- package/src/agent-execution/telemetry-bridge.js +15 -0
- package/src/agents.js +1 -1
- package/src/artifact-kinds.js +2 -1
- package/src/autopilot-signal.js +71 -0
- package/src/cli.js +31 -5
- package/src/commands/agent-execution.js +36 -0
- 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/op-capture.js +33 -3
- package/src/commands/op-reinforce.js +10 -22
- package/src/commands/update.js +5 -1
- package/src/commands/verification-plan.js +56 -12
- package/src/commands/verify-artifact.js +64 -1
- package/src/commands/workflow-execute.js +168 -36
- package/src/commands/workflow-next.js +60 -16
- package/src/doctor.js +4 -2
- package/src/harness/criteria-runner.js +4 -1
- 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/operator-memory/decision.js +41 -0
- package/src/parser.js +6 -0
- package/src/runtime-store.js +167 -8
- 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 +26 -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 +32 -16
- package/template/.aioson/docs/dev/phase-loop.md +9 -7
- package/template/.aioson/docs/play/llm-data-and-bindings.md +70 -7
- package/template/.aioson/schemas/agent-execution.schema.json +28 -0
- package/template/.aioson/skills/design/interface-design/SKILL.md +17 -0
- package/template/AGENTS.md +36 -36
- package/template/CLAUDE.md +1 -1
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const fs = require('node:fs/promises');
|
|
11
|
-
const path = require('node:path');
|
|
11
|
+
const path = require('node:path');
|
|
12
|
+
const { initManifest, loadManifest } = require('../agent-execution/manifest');
|
|
12
13
|
const {
|
|
13
14
|
detectClassification,
|
|
14
15
|
scanArtifacts,
|
|
@@ -27,6 +28,7 @@ const { readHandoff, readHandoffProtocol } = require('../session-handoff');
|
|
|
27
28
|
const {
|
|
28
29
|
STATE_RELATIVE_PATH,
|
|
29
30
|
buildDefaultWorkflowConfig,
|
|
31
|
+
detectWorkflowMode,
|
|
30
32
|
readWorkflowConfig,
|
|
31
33
|
runWorkflowNext
|
|
32
34
|
} = require('./workflow-next');
|
|
@@ -110,11 +112,26 @@ function isAgenticRequested(options = {}) {
|
|
|
110
112
|
options.agentic ||
|
|
111
113
|
options['agentic-run'] ||
|
|
112
114
|
options.autopilot === 'agentic' ||
|
|
113
|
-
options.autopilot === 'runtime'
|
|
115
|
+
options.autopilot === 'runtime' ||
|
|
116
|
+
// Seeding the scheme IS turning on autopilot — the whole point of --seed is to
|
|
117
|
+
// persist an enabled agentic_policy the interactive agents then follow.
|
|
118
|
+
options.seed ||
|
|
119
|
+
options['seed-only']
|
|
114
120
|
);
|
|
115
121
|
}
|
|
116
122
|
|
|
117
123
|
function buildAgenticPolicy(options = {}, classification = 'SMALL') {
|
|
124
|
+
// --seed --step: the per-feature "step by step" choice. Writes an explicitly
|
|
125
|
+
// DISARMED scheme — resolveAutopilotSignal and the agent triggers treat it as
|
|
126
|
+
// the feature-level override that wins over a project-wide auto_handoff: true.
|
|
127
|
+
if (options.step) {
|
|
128
|
+
return {
|
|
129
|
+
enabled: false,
|
|
130
|
+
mode: 'step_by_step',
|
|
131
|
+
source: 'workflow:execute --step'
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
118
135
|
const enabled = isAgenticRequested(options);
|
|
119
136
|
if (!enabled) return null;
|
|
120
137
|
|
|
@@ -229,6 +246,18 @@ function inferCompletedStagesFromArtifacts(sequence, artifacts, gates) {
|
|
|
229
246
|
inferred = Boolean(artifacts.requirements && artifacts.requirements.exists && gates.requirements === 'approved');
|
|
230
247
|
} else if (normalized === 'architect') {
|
|
231
248
|
inferred = Boolean(artifacts.architecture && artifacts.architecture.exists && gates.design === 'approved');
|
|
249
|
+
} else if (normalized === 'sheldon' || normalized === 'orchestrator') {
|
|
250
|
+
// Single spec authorities (lean @sheldon / maestro @orchestrator) collapse
|
|
251
|
+
// Gates A/B/C into one hop — done once the spec package exists with the
|
|
252
|
+
// collapsed gates approved. Without this, a seed run AFTER the spec stage
|
|
253
|
+
// finished reported `next: sheldon|orchestrator` and pointed the whole
|
|
254
|
+
// agentic scheme backwards.
|
|
255
|
+
inferred = Boolean(
|
|
256
|
+
artifacts.spec && artifacts.spec.exists &&
|
|
257
|
+
gates.requirements === 'approved' &&
|
|
258
|
+
gates.design === 'approved' &&
|
|
259
|
+
gates.plan === 'approved'
|
|
260
|
+
);
|
|
232
261
|
} else if (normalized === 'pm') {
|
|
233
262
|
inferred = Boolean(artifacts.implementation_plan && artifacts.implementation_plan.exists && gates.plan === 'approved');
|
|
234
263
|
} else if (normalized === 'qa') {
|
|
@@ -244,9 +273,17 @@ function inferCompletedStagesFromArtifacts(sequence, artifacts, gates) {
|
|
|
244
273
|
return completed;
|
|
245
274
|
}
|
|
246
275
|
|
|
247
|
-
|
|
276
|
+
/**
|
|
277
|
+
* Reads the persisted workflow state for a seed/preview of `slug`, applying the
|
|
278
|
+
* SAME staleness guard as loadOrCreateState: a state whose feature is no longer
|
|
279
|
+
* the features.md-active feature is stale and gets discarded (the loader would
|
|
280
|
+
* throw it away one command later anyway — hard-failing the seed on it just
|
|
281
|
+
* silently disarmed autopilot). The refusal survives only for a genuinely
|
|
282
|
+
* active different feature.
|
|
283
|
+
*/
|
|
284
|
+
async function resolveExistingFeatureState(targetDir, slug) {
|
|
248
285
|
const statePath = path.join(targetDir, STATE_RELATIVE_PATH);
|
|
249
|
-
|
|
286
|
+
let existing = await readJsonIfExists(statePath);
|
|
250
287
|
const focusStage = getFocusStage(existing);
|
|
251
288
|
|
|
252
289
|
if (
|
|
@@ -256,14 +293,41 @@ async function seedFeatureWorkflowState(targetDir, slug, classification, startFr
|
|
|
256
293
|
existing.featureSlug !== slug &&
|
|
257
294
|
focusStage
|
|
258
295
|
) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
296
|
+
let modeInfo = null;
|
|
297
|
+
try {
|
|
298
|
+
modeInfo = await detectWorkflowMode(targetDir);
|
|
299
|
+
} catch {
|
|
300
|
+
modeInfo = null;
|
|
301
|
+
}
|
|
302
|
+
const existingIsActive = Boolean(
|
|
303
|
+
modeInfo && modeInfo.mode === 'feature' && modeInfo.featureSlug === existing.featureSlug
|
|
304
|
+
);
|
|
305
|
+
const requestedIsActive = Boolean(
|
|
306
|
+
modeInfo && modeInfo.mode === 'feature' && modeInfo.featureSlug === slug
|
|
307
|
+
);
|
|
308
|
+
if (!existingIsActive || requestedIsActive) {
|
|
309
|
+
existing = null; // stale pointer — reseed for the requested feature
|
|
310
|
+
} else {
|
|
311
|
+
return {
|
|
312
|
+
existing: null,
|
|
313
|
+
refusal: {
|
|
314
|
+
ok: false,
|
|
315
|
+
reason: 'different_active_feature',
|
|
316
|
+
active_feature: existing.featureSlug,
|
|
317
|
+
active_stage: focusStage
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
}
|
|
265
321
|
}
|
|
266
322
|
|
|
323
|
+
return { existing, refusal: null };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function seedFeatureWorkflowState(targetDir, slug, classification, startFrom) {
|
|
327
|
+
const statePath = path.join(targetDir, STATE_RELATIVE_PATH);
|
|
328
|
+
const { existing, refusal } = await resolveExistingFeatureState(targetDir, slug);
|
|
329
|
+
if (refusal) return refusal;
|
|
330
|
+
|
|
267
331
|
if (existing && existing.mode === 'feature' && existing.featureSlug === slug) {
|
|
268
332
|
return {
|
|
269
333
|
ok: true,
|
|
@@ -312,23 +376,8 @@ async function seedFeatureWorkflowState(targetDir, slug, classification, startFr
|
|
|
312
376
|
}
|
|
313
377
|
|
|
314
378
|
async function previewFeatureWorkflowState(targetDir, slug, classification, startFrom) {
|
|
315
|
-
const existing = await
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (
|
|
319
|
-
existing &&
|
|
320
|
-
existing.mode === 'feature' &&
|
|
321
|
-
existing.featureSlug &&
|
|
322
|
-
existing.featureSlug !== slug &&
|
|
323
|
-
focusStage
|
|
324
|
-
) {
|
|
325
|
-
return {
|
|
326
|
-
ok: false,
|
|
327
|
-
reason: 'different_active_feature',
|
|
328
|
-
active_feature: existing.featureSlug,
|
|
329
|
-
active_stage: focusStage
|
|
330
|
-
};
|
|
331
|
-
}
|
|
379
|
+
const { existing, refusal } = await resolveExistingFeatureState(targetDir, slug);
|
|
380
|
+
if (refusal) return refusal;
|
|
332
381
|
|
|
333
382
|
if (existing && existing.mode === 'feature' && existing.featureSlug === slug) {
|
|
334
383
|
return {
|
|
@@ -539,7 +588,11 @@ function buildCheckpointPayload(activation, handoff, handoffProtocol) {
|
|
|
539
588
|
async function writeExecutionCheckpoint(targetDir, payload) {
|
|
540
589
|
const execPath = path.join(targetDir, EXECUTION_STATE_RELATIVE_PATH);
|
|
541
590
|
const existing = await readJsonIfExists(execPath);
|
|
542
|
-
|
|
591
|
+
// History is per-feature: never carry a previous feature's checkpoints into
|
|
592
|
+
// a new feature's scheme.
|
|
593
|
+
const history = existing && existing.feature === payload.feature && Array.isArray(existing.history)
|
|
594
|
+
? [...existing.history]
|
|
595
|
+
: [];
|
|
543
596
|
if (payload.checkpoint) {
|
|
544
597
|
history.push({
|
|
545
598
|
at: new Date().toISOString(),
|
|
@@ -699,9 +752,12 @@ async function performRunnerTransition(targetDir, suggestion, tool, requestedMod
|
|
|
699
752
|
async function runWorkflowExecute({ args, options = {}, logger }) {
|
|
700
753
|
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
701
754
|
const slug = options.feature ? String(options.feature).trim() : null;
|
|
702
|
-
const tool = options.tool ? String(options.tool).trim() : 'claude';
|
|
755
|
+
const tool = options.tool ? String(options.tool).trim() : 'claude';
|
|
703
756
|
const requestedMode = options.mode ? String(options.mode).trim() : null;
|
|
704
757
|
const dryRun = Boolean(options['dry-run'] || options.dry);
|
|
758
|
+
// --step (disarm) is by definition record-only: it writes the disarmed scheme
|
|
759
|
+
// and never drives stage transitions, with or without an explicit --seed.
|
|
760
|
+
const seedOnly = Boolean(options.seed || options['seed-only'] || options.step);
|
|
705
761
|
const startFrom = options['start-from'] ? String(options['start-from']).trim() : null;
|
|
706
762
|
const skipOptional = Boolean(options['skip-optional']);
|
|
707
763
|
const parsedMaxCheckpoints = Number.parseInt(String(options['max-checkpoints'] || '1'), 10);
|
|
@@ -733,9 +789,20 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
|
|
|
733
789
|
return failure;
|
|
734
790
|
}
|
|
735
791
|
|
|
736
|
-
const parallelGuard = laneIndex !== null
|
|
792
|
+
const parallelGuard = laneIndex !== null
|
|
737
793
|
? await runLaneGuardPreflight(targetDir, laneIndex)
|
|
738
|
-
: null;
|
|
794
|
+
: null;
|
|
795
|
+
|
|
796
|
+
let agentExecution = await loadManifest(targetDir, slug);
|
|
797
|
+
if (!dryRun && !agentExecution.exists) {
|
|
798
|
+
await initManifest(targetDir, slug, tool);
|
|
799
|
+
agentExecution = await loadManifest(targetDir, slug);
|
|
800
|
+
}
|
|
801
|
+
if (agentExecution.exists && !agentExecution.ok) {
|
|
802
|
+
const failure = { ok: false, reason: 'agent_execution_manifest_invalid', errors: agentExecution.errors, manifest_path: agentExecution.path };
|
|
803
|
+
if (!options.json) logger.error(`Invalid agent execution manifest: ${agentExecution.path}`);
|
|
804
|
+
return failure;
|
|
805
|
+
}
|
|
739
806
|
|
|
740
807
|
if (parallelGuard && !parallelGuard.ok && !parallelGuard.skipped) {
|
|
741
808
|
if (parallelGuard.reason === 'lane_not_found') {
|
|
@@ -792,14 +859,18 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
|
|
|
792
859
|
`--tool=${quoteCliArg(tool)}`,
|
|
793
860
|
...(requestedMode ? [`--mode=${quoteCliArg(requestedMode)}`] : []),
|
|
794
861
|
...(maxCheckpoints !== 1 ? [`--max-checkpoints=${quoteCliArg(maxCheckpoints)}`] : []),
|
|
795
|
-
|
|
796
|
-
|
|
862
|
+
// A seed run must resume as a seed run — `--agentic` (the CLI-advancing
|
|
863
|
+
// runner) is the opposite of the seed-only contract. A disarm run keeps
|
|
864
|
+
// its --step so replaying it never re-arms the scheme.
|
|
865
|
+
...(seedOnly ? ['--seed'] : agenticPolicy && agenticPolicy.enabled ? ['--agentic'] : []),
|
|
866
|
+
...(options.step ? ['--step'] : []),
|
|
867
|
+
...(agenticPolicy && agenticPolicy.review_cycle && agenticPolicy.review_cycle.max_dev_qa_cycles !== DEFAULT_AGENTIC_MAX_CYCLES
|
|
797
868
|
? [`--max-dev-qa-cycles=${quoteCliArg(agenticPolicy.review_cycle.max_dev_qa_cycles)}`]
|
|
798
869
|
: []),
|
|
799
|
-
...(agenticPolicy && agenticPolicy.review_cycle.max_tester_correction_cycles !== DEFAULT_AGENTIC_MAX_CYCLES
|
|
870
|
+
...(agenticPolicy && agenticPolicy.review_cycle && agenticPolicy.review_cycle.max_tester_correction_cycles !== DEFAULT_AGENTIC_MAX_CYCLES
|
|
800
871
|
? [`--max-tester-cycles=${quoteCliArg(agenticPolicy.review_cycle.max_tester_correction_cycles)}`]
|
|
801
872
|
: []),
|
|
802
|
-
...(agenticPolicy && agenticPolicy.review_cycle.max_pentester_correction_cycles !== DEFAULT_AGENTIC_MAX_CYCLES
|
|
873
|
+
...(agenticPolicy && agenticPolicy.review_cycle && agenticPolicy.review_cycle.max_pentester_correction_cycles !== DEFAULT_AGENTIC_MAX_CYCLES
|
|
803
874
|
? [`--max-pentester-cycles=${quoteCliArg(agenticPolicy.review_cycle.max_pentester_correction_cycles)}`]
|
|
804
875
|
: [])
|
|
805
876
|
].join(' ');
|
|
@@ -823,7 +894,8 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
|
|
|
823
894
|
status_snapshot: statusSnapshot,
|
|
824
895
|
suggestion: statusSnapshot && statusSnapshot.suggestion ? statusSnapshot.suggestion : null,
|
|
825
896
|
resume_command: resumeCommand,
|
|
826
|
-
agentic_policy: agenticPolicy,
|
|
897
|
+
agentic_policy: agenticPolicy,
|
|
898
|
+
agent_execution: agentExecution.exists ? { path: agentExecution.path, digest: agentExecution.digest } : { source: 'legacy' },
|
|
827
899
|
parallel_guard: parallelGuard
|
|
828
900
|
};
|
|
829
901
|
|
|
@@ -854,6 +926,66 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
|
|
|
854
926
|
return result;
|
|
855
927
|
}
|
|
856
928
|
|
|
929
|
+
// --seed: persist the workflow.state.json + workflow-execute.json (with an
|
|
930
|
+
// enabled agentic_policy) and STOP. Unlike a full run, it does not drive stage
|
|
931
|
+
// transitions from the CLI — in an interactive Claude Code session the agents
|
|
932
|
+
// themselves follow the scheme. This is what the spec->dev handoff calls so a
|
|
933
|
+
// feature built the normal way carries the autopilot contract without the user
|
|
934
|
+
// running anything. Idempotent: re-seeding the same slug reuses existing state.
|
|
935
|
+
if (seedOnly) {
|
|
936
|
+
const handoff = await readHandoff(targetDir);
|
|
937
|
+
const handoffProtocol = await readHandoffProtocol(targetDir);
|
|
938
|
+
const nextStage = seeded.state ? (seeded.state.current || seeded.state.next || null) : null;
|
|
939
|
+
const executionState = await writeExecutionCheckpoint(targetDir, {
|
|
940
|
+
feature: slug,
|
|
941
|
+
classification,
|
|
942
|
+
tool,
|
|
943
|
+
requestedMode,
|
|
944
|
+
resumed: seeded.resumed,
|
|
945
|
+
status: nextStage ? 'active' : 'completed',
|
|
946
|
+
checkpoint: buildCheckpointPayload(null, handoff, handoffProtocol),
|
|
947
|
+
statusSnapshot,
|
|
948
|
+
suggestion: statusSnapshot && statusSnapshot.suggestion ? statusSnapshot.suggestion : null,
|
|
949
|
+
resumeCommand,
|
|
950
|
+
agenticPolicy
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
const result = {
|
|
954
|
+
ok: true,
|
|
955
|
+
feature: slug,
|
|
956
|
+
classification,
|
|
957
|
+
tool,
|
|
958
|
+
requested_mode: requestedMode,
|
|
959
|
+
seeded: true,
|
|
960
|
+
resumed: seeded.resumed,
|
|
961
|
+
state_path: seeded.statePath,
|
|
962
|
+
execution_state_path: EXECUTION_STATE_RELATIVE_PATH,
|
|
963
|
+
next_stage: nextStage,
|
|
964
|
+
checkpoint: executionState.checkpoint,
|
|
965
|
+
execution_state: executionState,
|
|
966
|
+
status_snapshot: statusSnapshot,
|
|
967
|
+
suggestion: statusSnapshot && statusSnapshot.suggestion ? statusSnapshot.suggestion : null,
|
|
968
|
+
resume_command: resumeCommand,
|
|
969
|
+
agentic_policy: agenticPolicy,
|
|
970
|
+
agent_execution: { path: agentExecution.path, digest: agentExecution.digest },
|
|
971
|
+
parallel_guard: parallelGuard
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
if (options.json) return result;
|
|
975
|
+
|
|
976
|
+
logger.log('');
|
|
977
|
+
logger.log(`Agentic workflow scheme seeded → ${EXECUTION_STATE_RELATIVE_PATH}`);
|
|
978
|
+
logger.log(`Feature: ${slug} (${classification}) Next stage: @${nextStage || 'none'}`);
|
|
979
|
+
logger.log(
|
|
980
|
+
`Autopilot: ${agenticPolicy && agenticPolicy.enabled
|
|
981
|
+
? 'enabled — interactive agents run the chain to feature:close (human gate)'
|
|
982
|
+
: 'disabled'}`
|
|
983
|
+
);
|
|
984
|
+
for (const line of formatAgenticPolicyLines(agenticPolicy)) logger.log(line);
|
|
985
|
+
logger.log('');
|
|
986
|
+
return result;
|
|
987
|
+
}
|
|
988
|
+
|
|
857
989
|
const executionTransitions = [];
|
|
858
990
|
let activation = null;
|
|
859
991
|
let currentStatus = statusSnapshot;
|
|
@@ -16,6 +16,7 @@ const { logError, buildHealingPrompt } = require('../self-healing');
|
|
|
16
16
|
const { validateHandoffProtocol } = require('../handoff-validator');
|
|
17
17
|
const { readAutonomyProtocol, resolveEffectiveMode } = require('../autonomy-policy');
|
|
18
18
|
const { readAgentManifest, buildAgentCapabilitySummary } = require('../agent-manifests');
|
|
19
|
+
const { resolveAutopilotSignal } = require('../autopilot-signal');
|
|
19
20
|
const { runMemoryReflectPrepare } = require('./memory-reflect-prepare');
|
|
20
21
|
const { inspectStagedChanges } = require('../lib/git-commit-guard');
|
|
21
22
|
const { emitSecurityRuntimeEvent } = require('../lib/security/runtime-events');
|
|
@@ -61,19 +62,18 @@ const DEFAULT_FEATURE_WORKFLOW_BY_CLASSIFICATION = {
|
|
|
61
62
|
MEDIUM: ['product', 'orchestrator', 'dev', 'pentester', 'qa']
|
|
62
63
|
};
|
|
63
64
|
|
|
64
|
-
// Stages eligible for autopilot handoff
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
// pre-dev — @sheldon is the only pre-dev agent and hands off by hand — and is
|
|
75
|
-
// active only on the post-dev cycle (dev → qa). This is by design, not an omission.
|
|
65
|
+
// Stages eligible for autopilot handoff — the FULL feature chain (see
|
|
66
|
+
// .aioson/docs/autopilot-handoff.md). Activation = auto_handoff: true in
|
|
67
|
+
// project.context.md OR the seeded scheme (resolveAutopilotSignal). Two segments:
|
|
68
|
+
// 1. spec → dev chain: @product seeds the agentic scheme and invokes the spec
|
|
69
|
+
// authority (@sheldon lean / @orchestrator maestro), which crosses into
|
|
70
|
+
// @dev via the dev-state.md cold-start packet once its own gates/decisions
|
|
71
|
+
// are settled. Detour agents (analyst/architect/pm/...) chain only when an
|
|
72
|
+
// opt-in detour adds them to the active sequence.
|
|
73
|
+
// 2. post-dev review cycle: @dev → @qa → @tester/@pentester (when their @qa
|
|
74
|
+
// triggers fire) → @validator → STOPS before feature:close (human gate).
|
|
76
75
|
const AUTOPILOT_HANDOFF_STAGES = new Set([
|
|
76
|
+
'product', 'sheldon', 'orchestrator',
|
|
77
77
|
'analyst', 'scope-check', 'architect', 'discovery-design-doc', 'pm',
|
|
78
78
|
'dev', 'qa', 'tester', 'pentester', 'validator'
|
|
79
79
|
]);
|
|
@@ -99,6 +99,14 @@ function isMaestroOrchestratorState(state) {
|
|
|
99
99
|
return idx !== -1 && sequence[idx + 1] === 'dev';
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
// Lean lane (SMALL, sheldon → dev): @sheldon is the single spec authority.
|
|
103
|
+
// Mirrors isLeanSheldonState in handoff-contract.js.
|
|
104
|
+
function isLeanSheldonState(state) {
|
|
105
|
+
const sequence = Array.isArray(state && state.sequence) ? state.sequence.map(normalizeAgentName) : [];
|
|
106
|
+
const idx = sequence.indexOf('sheldon');
|
|
107
|
+
return idx !== -1 && sequence[idx + 1] === 'dev';
|
|
108
|
+
}
|
|
109
|
+
|
|
102
110
|
function buildDefaultWorkflowConfig() {
|
|
103
111
|
return {
|
|
104
112
|
version: 1,
|
|
@@ -376,6 +384,29 @@ async function validateStageArtifacts(targetDir, state, stage) {
|
|
|
376
384
|
return true;
|
|
377
385
|
}
|
|
378
386
|
|
|
387
|
+
if (stage === 'sheldon') {
|
|
388
|
+
// Lean lane (SMALL, sheldon → dev): @sheldon is the single spec authority —
|
|
389
|
+
// "done" once the collapsed spec package exists (mirrors the orchestrator
|
|
390
|
+
// maestro branch below; the handoff contract re-checks Gates A/B/C). Without
|
|
391
|
+
// this branch nothing ever marks the sheldon stage resolved, and a later
|
|
392
|
+
// `--complete=dev` computes `next: sheldon` — the state machine walking
|
|
393
|
+
// BACKWARDS into the spec agent after implementation.
|
|
394
|
+
if (state.mode === 'feature' && slug && isLeanSheldonState(state)) {
|
|
395
|
+
const designDoc = [path.join(base, `design-doc-${slug}.md`), path.join(base, 'design-doc.md')];
|
|
396
|
+
const readiness = [path.join(base, `readiness-${slug}.md`), path.join(base, 'readiness.md')];
|
|
397
|
+
return (await exists(path.join(base, `sheldon-enrichment-${slug}.md`)))
|
|
398
|
+
&& (await exists(path.join(base, `requirements-${slug}.md`)))
|
|
399
|
+
&& (await exists(path.join(base, `spec-${slug}.md`)))
|
|
400
|
+
&& (await exists(path.join(base, `implementation-plan-${slug}.md`)))
|
|
401
|
+
&& (await anyExists(designDoc))
|
|
402
|
+
&& (await anyExists(readiness));
|
|
403
|
+
}
|
|
404
|
+
if (state.mode === 'feature' && slug) {
|
|
405
|
+
return await exists(path.join(base, `sheldon-enrichment-${slug}.md`));
|
|
406
|
+
}
|
|
407
|
+
return await exists(path.join(base, 'sheldon-enrichment.md'));
|
|
408
|
+
}
|
|
409
|
+
|
|
379
410
|
if (stage === 'orchestrator') {
|
|
380
411
|
// Maestro lane (MEDIUM, orchestrator → dev): the orchestrator is the single
|
|
381
412
|
// spec authority — "done" once the gated spec package exists (mirrors how the
|
|
@@ -519,7 +550,10 @@ function isInferableStage(stage) {
|
|
|
519
550
|
// could never infer scope-check as completed during stale-state recovery.
|
|
520
551
|
// pm is inferable from implementation-plan-{slug}.md for the same reason:
|
|
521
552
|
// it sits before scope-check in the MEDIUM feature sequence.
|
|
522
|
-
|
|
553
|
+
// sheldon (lean lane) and orchestrator (maestro lane) are the single spec
|
|
554
|
+
// authorities — both inferable from their collapsed spec package so stale-state
|
|
555
|
+
// recovery and mid-lane seeding never re-activate a finished spec stage.
|
|
556
|
+
return ['setup', 'product', 'analyst', 'scope-check', 'architect', 'discovery-design-doc', 'ux-ui', 'pm', 'sheldon', 'orchestrator'].includes(
|
|
523
557
|
normalizeAgentName(stage)
|
|
524
558
|
);
|
|
525
559
|
}
|
|
@@ -993,8 +1027,15 @@ async function finalizeCurrentStage(targetDir, config, state, stageName) {
|
|
|
993
1027
|
detour: null
|
|
994
1028
|
});
|
|
995
1029
|
|
|
1030
|
+
// Reconcile eagerly: completing a later stage must never leave `next` pointing
|
|
1031
|
+
// at an earlier unresolved stage (e.g. lean-lane sheldon that only chained via
|
|
1032
|
+
// prompt). Without this, `--complete=dev` re-activated the spec agent and the
|
|
1033
|
+
// reconcile only healed on the NEXT load — after the backwards activation had
|
|
1034
|
+
// already been printed and persisted.
|
|
1035
|
+
const reconciled = reconcileWorkflowState(nextState);
|
|
1036
|
+
|
|
996
1037
|
return {
|
|
997
|
-
state: nextState,
|
|
1038
|
+
state: reconciled.changed ? reconciled.state : nextState,
|
|
998
1039
|
completedStage: normalizedStage,
|
|
999
1040
|
...(auditCodeSummary ? { auditCode: auditCodeSummary } : {})
|
|
1000
1041
|
};
|
|
@@ -1560,9 +1601,12 @@ async function activateStage(
|
|
|
1560
1601
|
state.mode === 'feature' &&
|
|
1561
1602
|
(state.classification === 'SMALL' || state.classification === 'MEDIUM')
|
|
1562
1603
|
) {
|
|
1604
|
+
// Frontmatter flag OR the seeded scheme (slug-scoped) — the per-feature
|
|
1605
|
+
// "Autopilot" choice only seeds workflow-execute.json and never writes
|
|
1606
|
+
// auto_handoff, so reading the frontmatter alone silently disabled it.
|
|
1563
1607
|
try {
|
|
1564
|
-
const
|
|
1565
|
-
autoHandoff =
|
|
1608
|
+
const signal = await resolveAutopilotSignal(targetDir, { slug: state.featureSlug });
|
|
1609
|
+
autoHandoff = signal.enabled;
|
|
1566
1610
|
} catch {
|
|
1567
1611
|
autoHandoff = false;
|
|
1568
1612
|
}
|
package/src/doctor.js
CHANGED
|
@@ -228,8 +228,10 @@ async function runDoctor(targetDir) {
|
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
// Autopilot handoff: protocol doc installed but flag never declared in the
|
|
231
|
-
// context frontmatter
|
|
232
|
-
//
|
|
231
|
+
// context frontmatter. Absent is no longer "silently manual" — @product asks
|
|
232
|
+
// the run mode on screen at each feature kickoff. Declaring an explicit
|
|
233
|
+
// true/false sets a project default (skip the kickoff question) and passes;
|
|
234
|
+
// the advisory just surfaces that a default can be set.
|
|
233
235
|
const autopilotDocExists = await exists(path.join(targetDir, '.aioson/docs/autopilot-handoff.md'));
|
|
234
236
|
if (autopilotDocExists && contextValidation.exists && contextValidation.data) {
|
|
235
237
|
const autoHandoffDeclared = Object.prototype.hasOwnProperty.call(contextValidation.data, 'auto_handoff');
|
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
|
|
17
17
|
const crypto = require('node:crypto');
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
// Repository-wide quality gates can legitimately exceed two minutes on
|
|
20
|
+
// Windows. Keep the CLI --timeout override, but give deterministic contract
|
|
21
|
+
// checks enough headroom to avoid classifying a healthy full suite as failed.
|
|
22
|
+
const DEFAULT_CHECK_TIMEOUT_MS = 300000;
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
25
|
* Normaliza uma linha de erro para assinatura estável (D7):
|
package/src/i18n/messages/en.js
CHANGED
|
@@ -412,6 +412,7 @@ module.exports = {
|
|
|
412
412
|
update: {
|
|
413
413
|
not_installed: 'No AIOSON installation found in {targetDir}.',
|
|
414
414
|
done_at: 'Update completed at: {targetDir}',
|
|
415
|
+
template_version: 'Template version applied: {version}',
|
|
415
416
|
files_updated: 'Files updated: {count}',
|
|
416
417
|
backups_created: 'Backups created: {count}',
|
|
417
418
|
profile_renamed: 'i Profile `beginner` renamed to `creator` in project.context.md to better describe the user. Behavior unchanged. Edit the file to switch to `developer` if desired.',
|
|
@@ -491,7 +492,7 @@ module.exports = {
|
|
|
491
492
|
features_dir_present: 'Features directory present (.aioson/context/features/)',
|
|
492
493
|
features_dir_present_hint: 'Create .aioson/context/features/ to host per-feature dossiers (doctor --fix will create it).',
|
|
493
494
|
auto_handoff_declared: 'Autopilot handoff flag declared (auto_handoff in project.context.md)',
|
|
494
|
-
auto_handoff_declared_hint: 'The autopilot-handoff protocol is installed
|
|
495
|
+
auto_handoff_declared_hint: 'The autopilot-handoff protocol is installed and auto_handoff is not set — @product asks the run mode on screen at each feature kickoff (Autopilot / Step by step / Always). Set auto_handoff: true to always autopilot and skip the question, or false to always go step-by-step.',
|
|
495
496
|
claude_commands_present: 'Claude slash commands present ({missing} missing of {required})',
|
|
496
497
|
claude_commands_present_hint: 'Missing: {paths}. Run `aioson doctor . --fix` to restore them from the template.',
|
|
497
498
|
version_drift: 'CLI version matches project.context.md (context: {context}, CLI: {cli})',
|
package/src/i18n/messages/es.js
CHANGED
|
@@ -280,6 +280,7 @@ module.exports = {
|
|
|
280
280
|
update: {
|
|
281
281
|
not_installed: 'No se encontro instalacion de AIOSON en {targetDir}.',
|
|
282
282
|
done_at: 'Actualizacion completada en: {targetDir}',
|
|
283
|
+
template_version: 'Version de template aplicada: {version}',
|
|
283
284
|
files_updated: 'Archivos actualizados: {count}',
|
|
284
285
|
backups_created: 'Backups creados: {count}',
|
|
285
286
|
profile_renamed: 'i Perfil `beginner` renombrado a `creator` en project.context.md para describir mejor al usuario. Comportamiento sin cambios. Edita el archivo para cambiar a `developer` si lo prefieres.',
|
|
@@ -365,7 +366,7 @@ module.exports = {
|
|
|
365
366
|
features_dir_present: 'Directorio de features presente (.aioson/context/features/)',
|
|
366
367
|
features_dir_present_hint: 'Cree .aioson/context/features/ para hospedar dossiers por feature (doctor --fix lo crea).',
|
|
367
368
|
auto_handoff_declared: 'Flag de autopilot handoff declarada (auto_handoff en project.context.md)',
|
|
368
|
-
auto_handoff_declared_hint: 'El protocolo autopilot-handoff esta instalado
|
|
369
|
+
auto_handoff_declared_hint: 'El protocolo autopilot-handoff esta instalado y auto_handoff no esta definido — @product pregunta el modo en pantalla al inicio de cada feature (Autopilot / Paso a paso / Siempre). Defina auto_handoff: true para siempre autopilot y omitir la pregunta, o false para siempre paso a paso.',
|
|
369
370
|
claude_commands_present: 'Slash commands de Claude presentes ({missing} ausentes de {required})',
|
|
370
371
|
claude_commands_present_hint: 'Ausentes: {paths}. Ejecute `aioson doctor . --fix` para restaurarlos.',
|
|
371
372
|
version_drift: 'Version del CLI coincide con project.context.md (contexto: {context}, CLI: {cli})',
|
package/src/i18n/messages/fr.js
CHANGED
|
@@ -279,6 +279,7 @@ module.exports = {
|
|
|
279
279
|
update: {
|
|
280
280
|
not_installed: 'Aucune installation AIOSON trouvee dans {targetDir}.',
|
|
281
281
|
done_at: 'Mise a jour terminee dans : {targetDir}',
|
|
282
|
+
template_version: 'Version du template appliquee : {version}',
|
|
282
283
|
files_updated: 'Fichiers mis a jour : {count}',
|
|
283
284
|
backups_created: 'Sauvegardes creees : {count}',
|
|
284
285
|
profile_renamed: 'i Profil `beginner` renomme en `creator` dans project.context.md pour mieux decrire l utilisateur. Comportement inchange. Modifiez le fichier pour passer a `developer` si vous le souhaitez.',
|
|
@@ -364,7 +365,7 @@ module.exports = {
|
|
|
364
365
|
features_dir_present: 'Repertoire features present (.aioson/context/features/)',
|
|
365
366
|
features_dir_present_hint: 'Creez .aioson/context/features/ pour heberger les dossiers par feature (doctor --fix le cree).',
|
|
366
367
|
auto_handoff_declared: 'Flag autopilot handoff declare (auto_handoff dans project.context.md)',
|
|
367
|
-
auto_handoff_declared_hint: 'Le protocole autopilot-handoff est installe
|
|
368
|
+
auto_handoff_declared_hint: 'Le protocole autopilot-handoff est installe et auto_handoff n\'est pas defini — @product demande le mode a l\'ecran au debut de chaque feature (Autopilot / Pas a pas / Toujours). Definissez auto_handoff: true pour toujours autopilot et sauter la question, ou false pour toujours pas a pas.',
|
|
368
369
|
claude_commands_present: 'Slash commands de Claude presents ({missing} absents sur {required})',
|
|
369
370
|
claude_commands_present_hint: 'Absents : {paths}. Executez `aioson doctor . --fix` pour les restaurer.',
|
|
370
371
|
version_drift: 'Version du CLI conforme a project.context.md (contexte : {context}, CLI : {cli})',
|
|
@@ -379,6 +379,7 @@ module.exports = {
|
|
|
379
379
|
update: {
|
|
380
380
|
not_installed: 'Nenhuma instalacao do AIOSON encontrada em {targetDir}.',
|
|
381
381
|
done_at: 'Atualizacao concluida em: {targetDir}',
|
|
382
|
+
template_version: 'Versao do template aplicada: {version}',
|
|
382
383
|
files_updated: 'Arquivos atualizados: {count}',
|
|
383
384
|
backups_created: 'Backups criados: {count}',
|
|
384
385
|
profile_renamed: 'i Perfil `beginner` renomeado para `creator` em project.context.md para descrever melhor o usuario. Comportamento inalterado. Edite o arquivo para mudar para `developer` se preferir.',
|
|
@@ -465,7 +466,7 @@ module.exports = {
|
|
|
465
466
|
features_dir_present: 'Diretorio de features presente (.aioson/context/features/)',
|
|
466
467
|
features_dir_present_hint: 'Crie .aioson/context/features/ para hospedar dossies por feature (doctor --fix cria automaticamente).',
|
|
467
468
|
auto_handoff_declared: 'Flag de autopilot handoff declarada (auto_handoff no project.context.md)',
|
|
468
|
-
auto_handoff_declared_hint: 'O protocolo autopilot-handoff esta instalado
|
|
469
|
+
auto_handoff_declared_hint: 'O protocolo autopilot-handoff esta instalado e auto_handoff nao esta definido — o @product pergunta o modo na tela no inicio de cada feature (Autopilot / Passo a passo / Sempre). Defina auto_handoff: true para sempre autopilot e pular a pergunta, ou false para sempre passo a passo.',
|
|
469
470
|
claude_commands_present: 'Slash commands do Claude presentes ({missing} ausentes de {required})',
|
|
470
471
|
claude_commands_present_hint: 'Ausentes: {paths}. Rode `aioson doctor . --fix` para restaurar a partir do template.',
|
|
471
472
|
version_drift: 'Versao do CLI bate com project.context.md (contexto: {context}, CLI: {cli})',
|
|
@@ -39,7 +39,10 @@ function summarizeSkippedChanges(feedback) {
|
|
|
39
39
|
return [...sectionChanges, ...decisionChanges];
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
// Declining never writes to briefings.md, so a stale source hash is irrelevant
|
|
43
|
+
// to safety — validating it would only dead-end the user whose briefing moved
|
|
44
|
+
// on. Hence allowStale defaults to TRUE here (and only here).
|
|
45
|
+
async function applyDeclinedFeedback(projectDir, slug, feedback, { allowStale = true } = {}) {
|
|
43
46
|
const briefingPath = resolveBriefingPath(projectDir, slug, 'briefings.md');
|
|
44
47
|
const currentMarkdown = await fs.readFile(briefingPath, 'utf8');
|
|
45
48
|
const currentSourceHash = hashText(currentMarkdown);
|
|
@@ -57,10 +60,12 @@ async function applyDeclinedFeedback(projectDir, slug, feedback, { allowStale =
|
|
|
57
60
|
source_hash: feedback.source_hash,
|
|
58
61
|
applied_hash: currentSourceHash,
|
|
59
62
|
status: 'declined',
|
|
63
|
+
round: feedback.round || 1,
|
|
60
64
|
applied_changes: [],
|
|
61
65
|
skipped_changes: skippedChanges,
|
|
62
66
|
unresolved_comments: unresolvedComments,
|
|
63
67
|
blocking_items: feedback.blocking_items || [],
|
|
68
|
+
findings: feedback.findings || [],
|
|
64
69
|
next_action: 'rerun_review'
|
|
65
70
|
});
|
|
66
71
|
|
|
@@ -104,15 +109,22 @@ async function applyConfirmedFeedback(projectDir, slug, feedback, { confirmed =
|
|
|
104
109
|
if (returnedToDraft) {
|
|
105
110
|
returnApprovedBriefingToDraft(registry, slug);
|
|
106
111
|
}
|
|
112
|
+
// A blocking finding the user left pending blocks the PRD the same way an
|
|
113
|
+
// explicit blocking item does — even if the export step never mirrored it
|
|
114
|
+
// into blocking_items (hand-written feedback).
|
|
115
|
+
const pendingBlockingFindings = (feedback.findings || []).filter(
|
|
116
|
+
(finding) => finding.blocking && finding.status === 'pending'
|
|
117
|
+
);
|
|
118
|
+
const hasBlockers = (feedback.blocking_items || []).length > 0 || pendingBlockingFindings.length > 0;
|
|
107
119
|
markRefinementState(registry, slug, {
|
|
108
|
-
refinement_status:
|
|
120
|
+
refinement_status: hasBlockers ? 'blocked' : 'applied',
|
|
109
121
|
review_html: `.aioson/briefings/${slug}/review.html`,
|
|
110
122
|
refinement_report: `.aioson/briefings/${slug}/refinement-report.md`
|
|
111
123
|
});
|
|
112
124
|
await writeBriefingRegistry(projectDir, registry);
|
|
113
125
|
|
|
114
126
|
const unresolvedComments = (feedback.comments || []).filter((comment) => !comment.resolved);
|
|
115
|
-
const nextAction =
|
|
127
|
+
const nextAction = hasBlockers ? 'resolve_blockers' : 'approve_briefing';
|
|
116
128
|
const report = buildRefinementReport({
|
|
117
129
|
briefing_slug: slug,
|
|
118
130
|
source_briefing_path: `.aioson/briefings/${slug}/briefings.md`,
|
|
@@ -120,15 +132,17 @@ async function applyConfirmedFeedback(projectDir, slug, feedback, { confirmed =
|
|
|
120
132
|
source_hash: feedback.source_hash,
|
|
121
133
|
applied_hash: appliedHash,
|
|
122
134
|
status: 'applied',
|
|
135
|
+
round: feedback.round || 1,
|
|
123
136
|
applied_changes: appliedChanges,
|
|
124
137
|
skipped_changes: [],
|
|
125
138
|
unresolved_comments: unresolvedComments,
|
|
126
139
|
blocking_items: feedback.blocking_items || [],
|
|
140
|
+
findings: feedback.findings || [],
|
|
127
141
|
next_action: nextAction
|
|
128
142
|
});
|
|
129
143
|
await fs.writeFile(resolveBriefingPath(projectDir, slug, 'refinement-report.md'), report, 'utf8');
|
|
130
144
|
|
|
131
|
-
return { ok: true, appliedChanges, nextAction, appliedHash, returnedToDraft };
|
|
145
|
+
return { ok: true, appliedChanges, nextAction, appliedHash, returnedToDraft, pendingBlockingFindings: pendingBlockingFindings.length };
|
|
132
146
|
}
|
|
133
147
|
|
|
134
148
|
module.exports = { applyConfirmedFeedback, applyDeclinedFeedback };
|