elanous 0.2.16 → 0.2.17
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/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/{1307-b46d45b705e8c9ae.js → 1307-aac7332576c4d6fc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{6877-7c6208705c284124.js → 6877-fe94e76a5841ee9b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8401-4ce1c42c8d89903f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/899-f41be85e8a680651.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-3143b07a817d1da9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-3cb52e5ea8fc2fa5.js → page-17ba086ef82d339b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-7eb661ff23af7da2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-6b8444914da74ca6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-482c5dbde699947c.js → page-6e5c3a867557298b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/{page-1e962b31d1f11081.js → page-7110f732f45bf944.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-8c2d9526164f5d09.js → page-9a41d65355fc25b9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-5e77cd1802567c89.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-41afec0b255ed2bb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-c53598cfce6156f2.js → page-4187666132fc98c4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-13a63953f53c6a23.js → page-09328d499162bae4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-5d3c8429d04f3831.js → page-29ff3a8f84f03b6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-3193bdc0bbab047a.js → page-80ec7401bc83f511.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/{page-76270efaee638f17.js → page-ab93b240f687d69a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-6107ca8ddd5e3bdd.js → page-fc7d86467291ae38.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-dce686e582c23937.js → layout-de43a186e5ba5683.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/{page-d3d53b146416e422.js → page-8b08cafad27759c9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-0e2d6985fb42cd2f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-13e4108999f58d13.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e8296ee284aa1293.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-4167348049166852.js → page-043a9111e74f6b24.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-de52e8fdf9095f7a.js → not-found-23a4601ceb7f452e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-1ed1324b1dc36754.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-ca3952cd78a83ca2.js → page-10d958605a77b3bd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-5b042736aa113756.js → page-166745e51ac0c7b0.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-acb749f1e432bcf3.js → page-a04260b8ccc968d4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-7a1d85e121065cc8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-1f50e3d4e13435f4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-1ebfe049c640e171.js → page-12916918988ac97e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d8a3347a9d0c3f4a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-a9117d2cd3093d36.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-31fa1ce4c4aff9ae.js → page-5a64cb5208c6a8fd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-0f5cd57ca928f42b.js → page-1407ec83ea64c1f9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-abe8e3b74bea1ffc.js → page-1d824510df025d18.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-6e878ebbe1737d00.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-d28573d6792b4275.js → page-7f49fe645d5132f7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-59f03fda76c03d22.js → page-81a4e7f8dbfe74cb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-d751de5f8062ff5e.js → page-27d71f131acd9320.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-3ab59d19913b200e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-bf85b2f5bf077925.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-424659bacfb8cdd7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-ab32a8f522a60371.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-d4aef67abf264874.js → page-fc2df261f8a2c7df.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-1ea64acadd704ace.js → page-09461366af82ef13.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-3544f154c87b43ee.js → page-5d4fef9ccd7d406b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-30c2073bfc21c004.js → page-ea90a8ac73ce2749.js} +1 -1
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +13 -13
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +13 -13
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +13 -13
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +13 -13
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +13 -13
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +13 -13
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +13 -13
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +13 -13
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +13 -13
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +13 -13
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +13 -13
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +13 -13
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +13 -13
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +13 -13
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +13 -13
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +13 -13
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +13 -13
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +13 -13
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +13 -13
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +13 -13
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +13 -13
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +13 -13
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +13 -13
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +13 -13
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +13 -13
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +13 -13
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +14 -14
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +13 -13
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +13 -13
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +13 -13
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +13 -13
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +13 -13
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +13 -13
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +13 -13
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +13 -13
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +13 -13
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +13 -13
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +13 -13
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +13 -13
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +13 -13
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +13 -13
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +13 -13
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +13 -13
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +13 -13
- package/graphs/intake/intake-daily.yaml +9 -2
- package/graphs/intake/news-sources.yaml +4 -0
- package/graphs/intake/recipes.yaml +6 -1
- package/package.json +1 -1
- package/scripts/__fixtures__/intake-news.xml +7 -0
- package/scripts/intake-news.ts +169 -0
- package/scripts/release-loop/docs-land-node.ts +69 -10
- package/scripts/release-loop/publish-node.ts +2 -2
- package/scripts/release-loop/release-docs.ts +134 -0
- package/scripts/release-loop/version-node.ts +7 -4
- package/scripts/release-story/draft.ts +42 -14
- package/src/agent-mission/driver.ts +53 -10
- package/src/autopilot/track-agent.ts +5 -3
- package/src/cli/autopilot-cli.ts +66 -0
- package/src/cli/decisions-cli.ts +4 -1
- package/src/cli/intake-cli.ts +1 -1
- package/src/cli/log-event-names.ts +1 -0
- package/src/cli/pr-cli.ts +12 -1
- package/src/cli/provider-cli.ts +14 -0
- package/src/cli/self-update-canary.ts +46 -0
- package/src/cli/self-update.ts +200 -1
- package/src/context-card/index.ts +45 -0
- package/src/context-card/task.ts +76 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -0
- package/src/decisions/decision-ledger.ts +10 -1
- package/src/harness/harness-cli-command.ts +142 -10
- package/src/harness/harness-pod-dispatch.ts +11 -0
- package/src/harness/harness-queue.ts +86 -7
- package/src/harness/nested-elanous-policy.ts +23 -4
- package/src/hq/fence-wrapper.ts +123 -0
- package/src/hq/hq.ts +8 -0
- package/src/index.ts +57 -8
- package/src/intake-plane/digest.ts +39 -4
- package/src/nexus/api/grid.ts +61 -0
- package/src/nexus/api/http-server.ts +19 -1
- package/src/nexus/api/task-cards-api.ts +19 -0
- package/src/oauth/codex.ts +82 -2
- package/src/release-loop/landing-freeze.ts +18 -10
- package/src/seat-loop/seat-loop.ts +95 -31
- package/src/self-dev/ask-launch-flow.ts +26 -7
- package/src/self-dev/run-store.ts +5 -1
- package/src/self-dev/run-supervisor.ts +7 -1
- package/src/self-dev/supervisor-verdict-edges.ts +46 -0
- package/src/self-implement/frozen-merges.ts +25 -20
- package/src/self-implement/goal-author-clarification.ts +81 -0
- package/src/self-implement/goal-author-cli.ts +33 -5
- package/src/self-implement/goal-author.ts +88 -12
- package/src/self-implement/goal-clarification-escalation.ts +37 -1
- package/src/self-implement/goal-document-write.ts +28 -0
- package/src/self-implement/heal-intake.ts +4 -0
- package/src/self-implement/host-regate.ts +3 -1
- package/src/self-implement/orchestrator.ts +19 -1
- package/src/task-agent/actions.ts +118 -0
- package/src/task-agent/judge.ts +96 -0
- package/src/task-cards/card-store.ts +4 -1
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +33 -4
- package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
- package/src/task-orchestrator/surfaces/pod-pool.ts +81 -13
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +151 -29
- package/src/telegram-commands.ts +1 -1
- package/src/telegram-loops-command.ts +21 -14
- package/src/user-config.ts +25 -3
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9247-29d889b809288e65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-79c3ed84baaedd03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-c1e20d7d338713f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-a0e2a6d1bfb52fc5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-1a61c7ca1ceb3ae7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-51f811fa119308b6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-dacff46049e1557e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-36a7d994f4ca0299.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-57924393e3e4e539.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-fe1759f94003b32b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-d64c984e855c8c68.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-9afdee66b820c76e.js +0 -1
- /package/apps/pwa/out/_next/static/{rv9cierV0jSOvDBm1g0XB → yRA5ur6DcPl0YHSRheH2O}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{rv9cierV0jSOvDBm1g0XB → yRA5ur6DcPl0YHSRheH2O}/_ssgManifest.js +0 -0
|
@@ -681,6 +681,87 @@ export function injectGoalDocumentClarificationOtherAnswer(document: string, que
|
|
|
681
681
|
* 「답이 한 번도 안 들어가서 효과를 못 쟀다」***일 수 있다 — 두 무리 다 «답이 없는» 무리였다.
|
|
682
682
|
* ⛔ 무인 계약은 유지한다 — 대화형이 아니면 종전대로 지나가되 «그 사실을 값으로» 낸다.
|
|
683
683
|
*/
|
|
684
|
+
export type GoalClarificationSelfAnswerPath = 'say-first' | 'ask' | 'rework';
|
|
685
|
+
export type GoalClarificationSelfResolver = NonNullable<GoalAuthorClarificationResolutionDeps['selfResolve']>;
|
|
686
|
+
|
|
687
|
+
/** Attempt only unanswered document questions; retain every unanswered line if evidence is insufficient. */
|
|
688
|
+
export async function selfAnswerGoalDocumentClarifications(
|
|
689
|
+
document: string,
|
|
690
|
+
options: {
|
|
691
|
+
path: GoalClarificationSelfAnswerPath;
|
|
692
|
+
ask?: string;
|
|
693
|
+
evidence?: readonly string[];
|
|
694
|
+
resolver?: GoalClarificationSelfResolver;
|
|
695
|
+
},
|
|
696
|
+
): Promise<{ document: string; asked: number; answered: number; unanswered: number; newlyAnswered: number }> {
|
|
697
|
+
const clarifications = parseGoalDocumentClarifications(document);
|
|
698
|
+
const pending = clarifications.filter((item) => !item.answered);
|
|
699
|
+
// The first say pass may already have self-answered some questions while authoring: count those,
|
|
700
|
+
// never re-ask them, and still attempt every question that is left unanswered.
|
|
701
|
+
const alreadySelfAnswered = options.path === 'say-first'
|
|
702
|
+
? clarifications.filter((item) => item.answered && item.provenanceSource === 'self-authored').length
|
|
703
|
+
: 0;
|
|
704
|
+
let updated = document;
|
|
705
|
+
let answered = 0;
|
|
706
|
+
const evidence = options.evidence ?? document.split(/\r?\n/)
|
|
707
|
+
.filter((line) => /^(?:\s*- (?:\[code:|\[document:|TRACED PATHS|evidence:|Evidence:)|\s*-?\s*`?(?:src|test|scripts)\/[^\s`]+:\d+)/i.test(line))
|
|
708
|
+
.slice(0, 40);
|
|
709
|
+
for (const item of pending) {
|
|
710
|
+
if (parseGoalDocumentClarifications(updated).filter((entry) => entry.questionId === item.questionId).length !== 1) continue;
|
|
711
|
+
let response: Awaited<ReturnType<typeof resolveGoalAuthorClarification>>;
|
|
712
|
+
try { response = await resolveGoalAuthorClarification({
|
|
713
|
+
questionId: item.questionId,
|
|
714
|
+
kind: 'scope',
|
|
715
|
+
header: item.header,
|
|
716
|
+
question: item.question,
|
|
717
|
+
options: item.options.map((option) => ({ label: option.label, description: option.description })),
|
|
718
|
+
blocking: false,
|
|
719
|
+
}, {
|
|
720
|
+
selfResolve: options.resolver ?? defaultGoalAuthorSelfResolve,
|
|
721
|
+
evidence,
|
|
722
|
+
ask: options.ask,
|
|
723
|
+
selfResolutionSelected: true,
|
|
724
|
+
}); } catch {
|
|
725
|
+
// A resolver failure leaves this question unanswered; the remaining questions are still attempted.
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
if (response.answer === null || response.provenance?.source !== 'self-authored'
|
|
729
|
+
|| response.answer.startsWith(DEFERRED_ANSWER_PREFIX)) continue;
|
|
730
|
+
try {
|
|
731
|
+
if (!response.answer.trim() || /[\r\n]/.test(response.answer)
|
|
732
|
+
|| (!item.includeOther && !item.options.some((option) => option.label === response.answer))) continue;
|
|
733
|
+
const current = findUnansweredGoalDocumentClarification(updated, item.questionId);
|
|
734
|
+
const lines = updated.split(/\r?\n/);
|
|
735
|
+
const provenanceLines = current.provenanceLines ?? [];
|
|
736
|
+
for (const line of [...provenanceLines].sort((left, right) => right - left)) lines.splice(line, 1);
|
|
737
|
+
const answerLine = current.answerLine - provenanceLines.filter((line) => line < current.answerLine).length;
|
|
738
|
+
lines[answerLine] = ` - answer: ${response.answer}`;
|
|
739
|
+
lines.splice(answerLine + 1, 0,
|
|
740
|
+
' - provenance.source: self-authored',
|
|
741
|
+
...response.provenance.evidence!.map((entry) => ` - evidence: ${entry.replace(/[\r\n]/g, ' ')}`));
|
|
742
|
+
updateWhatToBuildRenderAnswers(lines, item.questionId, response.answer);
|
|
743
|
+
if (hasWhatToBuildUnansweredRenderAnswer(lines, item.questionId)) continue;
|
|
744
|
+
const candidate = lines.join(document.includes('\r\n') ? '\r\n' : '\n');
|
|
745
|
+
if (!parseGoalDocumentClarifications(candidate).some((entry) => entry.questionId === item.questionId
|
|
746
|
+
&& entry.answered && entry.answer === response.answer && entry.provenanceSource === 'self-authored')) continue;
|
|
747
|
+
updated = candidate;
|
|
748
|
+
answered += 1;
|
|
749
|
+
} catch {
|
|
750
|
+
// Leave malformed or disallowed answers deferred.
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
const asked = pending.length + alreadySelfAnswered;
|
|
754
|
+
const unanswered = pending.length - answered;
|
|
755
|
+
const newlyAnswered = answered;
|
|
756
|
+
answered += alreadySelfAnswered;
|
|
757
|
+
debug.log('self-implement.clarification-escalation', 'self-answer', {
|
|
758
|
+
path: options.path, asked, answered, unanswered,
|
|
759
|
+
reason: unanswered === 0 ? 'answered' : 'insufficient-evidence',
|
|
760
|
+
});
|
|
761
|
+
return { document: updated, asked, answered, unanswered, newlyAnswered };
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
|
|
684
765
|
export type ClarificationIntakeMode = 'none' | 'deferred-noninteractive' | 'ask';
|
|
685
766
|
|
|
686
767
|
export interface ClarificationIntakePlan {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { readFileSync, realpathSync } from 'node:fs';
|
|
2
|
+
import { writeGoalDocumentAtomic } from './goal-document-write.js';
|
|
2
3
|
import { isAbsolute, relative, resolve } from 'node:path';
|
|
3
4
|
import { debug } from '../debug/log.js';
|
|
5
|
+
import { getUserConfig } from '../user-config.js';
|
|
4
6
|
import { parseAskProseTitle } from '../self-dev/launch-preflight.js';
|
|
5
7
|
import { createGoalAuthorDecomposeSteps, readRecentStepCountsFailOpen, type RecentStepCountReader } from './goal-author-decompose.js';
|
|
6
|
-
import { defaultGoalAuthorSelfResolve, parseGoalDocumentClarifications, seedGoalAuthorFromAnsweredClarification, seedGoalAuthorFromClarification } from './goal-author-clarification.js';
|
|
8
|
+
import { defaultGoalAuthorSelfResolve, parseGoalDocumentClarifications, seedGoalAuthorFromAnsweredClarification, seedGoalAuthorFromClarification, selfAnswerGoalDocumentClarifications } from './goal-author-clarification.js';
|
|
7
9
|
import { classifyGoalInterviewRound, type GoalInterviewRoundStatus } from './goal-author-closure.js';
|
|
8
10
|
import { recordGoalAsk } from './goal-ask-store.js';
|
|
9
11
|
import { pressDecisionSignal } from './decision-signal-press.js';
|
|
@@ -15,6 +17,8 @@ import {
|
|
|
15
17
|
verbatimOriginalAsk,
|
|
16
18
|
writeAuthoredGoal,
|
|
17
19
|
type GoalAuthorDeps,
|
|
20
|
+
type GoalAuthorGradeSelection,
|
|
21
|
+
resolveGoalAuthorGrade,
|
|
18
22
|
type GoalType,
|
|
19
23
|
type GoalFileLintFinding,
|
|
20
24
|
} from './goal-author.js';
|
|
@@ -77,6 +81,7 @@ interface GoalAuthorCliOptions {
|
|
|
77
81
|
supersedes?: string;
|
|
78
82
|
rootIntent?: string;
|
|
79
83
|
goalType?: string;
|
|
84
|
+
authorGrade?: GoalAuthorGradeSelection;
|
|
80
85
|
selfResolveClarifications?: boolean;
|
|
81
86
|
adversarialReview?: boolean;
|
|
82
87
|
disableAdversarialReview?: boolean;
|
|
@@ -97,6 +102,7 @@ export interface GoalAuthorCliDeps {
|
|
|
97
102
|
recentStepCountReader?: RecentStepCountReader;
|
|
98
103
|
/** Resolver supplied only by the explicit CLI self-resolution opt-in. */
|
|
99
104
|
selfResolveClarification?: NonNullable<GoalAuthorDeps['selfResolveClarification']>;
|
|
105
|
+
writeFile?: (path: string, document: string) => void;
|
|
100
106
|
recordAsk?: typeof recordGoalAsk;
|
|
101
107
|
}
|
|
102
108
|
|
|
@@ -268,8 +274,9 @@ export async function runGoalAuthorCli(
|
|
|
268
274
|
: opts.reauthorFromAnsweredClarification
|
|
269
275
|
? { origin: 'answered-clarification-reauthor' }
|
|
270
276
|
: { origin: 'direct-request' };
|
|
271
|
-
const
|
|
272
|
-
const
|
|
277
|
+
const authorGrade = opts.authorGrade ?? resolveGoalAuthorGrade(undefined, getUserConfig().harness?.authorGrade);
|
|
278
|
+
const recentStepCounts = authorGrade.grade === 'lite' ? undefined : readRecentStepCountsFailOpen(deps.recentStepCountReader);
|
|
279
|
+
const decomposeSteps = authorGrade.grade === 'lite' ? undefined : deps.decomposeSteps ?? (deps.createDecomposeSteps ?? createGoalAuthorDecomposeSteps)(undefined, {
|
|
273
280
|
...(adversarialReview !== undefined ? { adversarialReview, adversarialReviewSource: 'cli' as const } : {}),
|
|
274
281
|
...(recentStepCounts !== undefined ? { recentStepCounts } : {}),
|
|
275
282
|
});
|
|
@@ -283,13 +290,34 @@ export async function runGoalAuthorCli(
|
|
|
283
290
|
debug.log('goal-author', 'goal-title-forwarded', proseTitle === undefined
|
|
284
291
|
? { passed: false }
|
|
285
292
|
: { passed: true, titleChars: proseTitle.length });
|
|
293
|
+
const gradeDeps = authorGrade.grade === 'lite' || opts.authorGrade ? { authorGrade } : {};
|
|
286
294
|
const authorDeps: Partial<GoalAuthorDeps> = parentGoalFile && parentQuestionId
|
|
287
|
-
? { parent: { goalFile: parentGoalFile, questionId: parentQuestionId }, parentDocument, clarificationAnswers, decomposeSteps, ...selfResolutionDeps, ...progressDeps, ...launchPreflightDeps, ...(opts.rootIntent !== undefined && { rootIntent: opts.rootIntent }), ...(goalType !== undefined && { goalType }), ...goalTitleDeps }
|
|
288
|
-
: { ...(clarificationAnswers && { clarificationAnswers }), decomposeSteps, ...selfResolutionDeps, ...progressDeps, ...launchPreflightDeps, ...(opts.rootIntent !== undefined && { rootIntent: opts.rootIntent }), ...(goalType !== undefined && { goalType }), ...goalTitleDeps };
|
|
295
|
+
? { parent: { goalFile: parentGoalFile, questionId: parentQuestionId }, parentDocument, clarificationAnswers, ...(decomposeSteps ? { decomposeSteps } : {}), ...gradeDeps, ...selfResolutionDeps, ...progressDeps, ...launchPreflightDeps, ...(opts.rootIntent !== undefined && { rootIntent: opts.rootIntent }), ...(goalType !== undefined && { goalType }), ...goalTitleDeps }
|
|
296
|
+
: { ...(clarificationAnswers && { clarificationAnswers }), ...(decomposeSteps ? { decomposeSteps } : {}), ...gradeDeps, ...selfResolutionDeps, ...progressDeps, ...launchPreflightDeps, ...(opts.rootIntent !== undefined && { rootIntent: opts.rootIntent }), ...(goalType !== undefined && { goalType }), ...goalTitleDeps };
|
|
289
297
|
const write = deps.write ?? writeAuthoredGoal;
|
|
290
298
|
const result = opts.groundingCwd === undefined
|
|
291
299
|
? await write(ask, opts.cwd, authorDeps, superseded ? { supersedes: superseded } : undefined)
|
|
292
300
|
: await write(ask, opts.cwd, authorDeps, superseded ? { supersedes: superseded } : undefined, opts.groundingCwd);
|
|
301
|
+
if (opts.selfResolveClarifications) {
|
|
302
|
+
const rework = Boolean(supersededDocument || opts.reauthorFromAnsweredClarification);
|
|
303
|
+
{
|
|
304
|
+
try {
|
|
305
|
+
const resolved = await selfAnswerGoalDocumentClarifications(result.authored.document, {
|
|
306
|
+
path: rework ? 'rework' : 'say-first', ask, resolver: deps.selfResolveClarification ?? defaultGoalAuthorSelfResolve,
|
|
307
|
+
evidence: result.authored.facts
|
|
308
|
+
? [...(result.authored.facts.persistentEvidence ?? []), ...result.authored.facts.codeFacts, ...result.authored.facts.documentFacts]
|
|
309
|
+
: undefined,
|
|
310
|
+
});
|
|
311
|
+
if (resolved.newlyAnswered > 0) {
|
|
312
|
+
(deps.writeFile ?? writeGoalDocumentAtomic)(result.path, resolved.document);
|
|
313
|
+
result.authored.document = resolved.document;
|
|
314
|
+
}
|
|
315
|
+
} catch (error) {
|
|
316
|
+
// The previous goal file is left intact (atomic replace); implementation proceeds from it with the questions deferred.
|
|
317
|
+
debug.log('goal-author.clarify', 'self-answer-deferred', { reason: error instanceof Error ? error.message : String(error) });
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
293
321
|
try {
|
|
294
322
|
const recorded = (deps.recordAsk ?? recordGoalAsk)({
|
|
295
323
|
authorRunId: result.authored.authorRunId,
|
|
@@ -18,6 +18,17 @@ function observeGoalAuthor(
|
|
|
18
18
|
debug.log(category === 'goal-author' ? 'harness.author' : 'harness.author.clarify', event, data);
|
|
19
19
|
}
|
|
20
20
|
import { getUserConfig } from '../user-config.js';
|
|
21
|
+
|
|
22
|
+
export type GoalAuthorGrade = 'full' | 'lite';
|
|
23
|
+
export type GoalAuthorGradeSource = 'flag' | 'config' | 'default';
|
|
24
|
+
export interface GoalAuthorGradeSelection { grade: GoalAuthorGrade; source: GoalAuthorGradeSource }
|
|
25
|
+
|
|
26
|
+
/** The flag is authoritative; malformed or absent configuration keeps the full pipeline. */
|
|
27
|
+
export function resolveGoalAuthorGrade(flag?: GoalAuthorGrade, configured?: unknown): GoalAuthorGradeSelection {
|
|
28
|
+
if (flag !== undefined) return { grade: flag, source: 'flag' };
|
|
29
|
+
if (configured === 'lite' || configured === 'full') return { grade: configured, source: 'config' };
|
|
30
|
+
return { grade: 'full', source: 'default' };
|
|
31
|
+
}
|
|
21
32
|
import type { ObserveOnlySource } from './observe-only.js';
|
|
22
33
|
import type { Question } from '../ask-user-question/types.js';
|
|
23
34
|
import { requestTestScenario as defaultRequestTestScenario, type TestScenarioRequestInput, type TestScenarioRequestResult } from './test-scenario-request.js';
|
|
@@ -45,6 +56,7 @@ type GoalEnhancement = Omit<Pick<EnhanceResult, 'original' | 'checklist' | 'verb
|
|
|
45
56
|
export interface GoalAuthorDeps {
|
|
46
57
|
ground: (ask: string, groundingDeps?: GoalAuthorGroundingDeps) => Promise<CodebaseGrounding>;
|
|
47
58
|
persistentGrounding?: GoalAuthorGroundingDeps;
|
|
59
|
+
authorGrade?: GoalAuthorGradeSelection;
|
|
48
60
|
enhance: (ask: string, opts?: EnhanceOpts) => Promise<GoalEnhancement>;
|
|
49
61
|
/** Additive enhancer options supplied by the file-writing entrypoint. */
|
|
50
62
|
enhanceOpts?: EnhanceOpts;
|
|
@@ -4925,6 +4937,17 @@ async function authorGoalWithSupersededRootIntent(
|
|
|
4925
4937
|
}
|
|
4926
4938
|
// ⭐ 이 저작 «한 번»의 id. goalId 보다 «먼저» 있어야 ground·enhance 의 소요를 그 골에 붙일 수 있다.
|
|
4927
4939
|
const authorRunId = generateGoalId();
|
|
4940
|
+
const authorGrade = deps.authorGrade ?? resolveGoalAuthorGrade(undefined, getUserConfig().harness?.authorGrade);
|
|
4941
|
+
const lite = authorGrade.grade === 'lite';
|
|
4942
|
+
// lite 는 판정선을 남기는 대신 단계를 덜 한다 — 요청에 반증 줄이 하나도 없으면 판정선 없는 골이 나가므로 시작 전에 멈춘다.
|
|
4943
|
+
if (lite && !ask.split(/\r?\n/u).some((line) => /판정선|판정 신호|반증|decision signal/iu.test(line))) {
|
|
4944
|
+
try { observeGoalAuthor('goal-author', 'author-grade-refused', { authorRunId, reason: 'lite-without-falsifiable-signal' }); }
|
|
4945
|
+
catch { /* observation is fail-soft */ }
|
|
4946
|
+
throw new Error('lite authoring needs a falsifiable signal line (판정선/반증) in the ask — add one or use --author-grade full');
|
|
4947
|
+
}
|
|
4948
|
+
const skippedPhases = lite ? ['adversarial-review', 'goal-steps-decomposed', 'persistent-grounding'] : [];
|
|
4949
|
+
try { observeGoalAuthor('goal-author', 'author-grade', { authorRunId, grade: authorGrade.grade, source: authorGrade.source, skippedPhases }); }
|
|
4950
|
+
catch { /* observation is fail-soft */ }
|
|
4928
4951
|
const authorStartedAt = Date.now();
|
|
4929
4952
|
const enhanceTokens: GoalAuthorTokens = { inputTokens: null, outputTokens: null };
|
|
4930
4953
|
const onEnhanceUsage = (usage: LLMUsage): void => {
|
|
@@ -4934,18 +4957,20 @@ async function authorGoalWithSupersededRootIntent(
|
|
|
4934
4957
|
};
|
|
4935
4958
|
let facts: CodebaseGrounding | null = null;
|
|
4936
4959
|
let groundingError = false;
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4960
|
+
if (!lite) {
|
|
4961
|
+
const persistentGrounding = resolveGoalAuthorPersistentGrounding('persistentGrounding' in deps ? deps.persistentGrounding : undefined);
|
|
4962
|
+
const groundStartedAt = startGoalAuthorPhase('ground', authorRunId, deps.onProgress);
|
|
4963
|
+
try {
|
|
4964
|
+
facts = await deps.ground(ask, persistentGrounding.deps);
|
|
4965
|
+
} catch {
|
|
4966
|
+
groundingError = true;
|
|
4967
|
+
} finally {
|
|
4968
|
+
endGoalAuthorPhase('ground', groundStartedAt, authorRunId, deps.onProgress);
|
|
4969
|
+
observeGoalAuthorPersistentGroundingResult(
|
|
4970
|
+
persistentGrounding.decision,
|
|
4971
|
+
goalAuthorPersistentGroundingMetrics(facts, groundingError, authorRunId),
|
|
4972
|
+
);
|
|
4973
|
+
}
|
|
4949
4974
|
}
|
|
4950
4975
|
|
|
4951
4976
|
const enhanceStartedAt = startGoalAuthorPhase('enhance', authorRunId, deps.onProgress);
|
|
@@ -4989,6 +5014,57 @@ async function authorGoalWithSupersededRootIntent(
|
|
|
4989
5014
|
}
|
|
4990
5015
|
enhancement = { ...enhancement, original: ask };
|
|
4991
5016
|
|
|
5017
|
+
if (lite) {
|
|
5018
|
+
const goalType = deps.goalType ?? 'implement';
|
|
5019
|
+
const goalId = deps.goalId ?? generateGoalId();
|
|
5020
|
+
const signal = decisionSignalSection(null, ask, enhancement.decisionSignal);
|
|
5021
|
+
const falsifiableSignal = signal.filled > 0 ? signal.lines : [
|
|
5022
|
+
...signal.lines,
|
|
5023
|
+
...ask.split(/\r?\n/u).filter((line) => /판정선|판정 신호|반증|decision signal/iu.test(line) && !signal.lines.some((rendered) => rendered.includes(line.trim()))),
|
|
5024
|
+
];
|
|
5025
|
+
const targetPaths = [...new Set(declaredTargetPaths(ask))];
|
|
5026
|
+
const checklistIds = [...new Set(Array.from(ask.matchAll(/\b[A-Z][A-Z0-9]+-[A-Z0-9]+\b/g), (match) => match[0]))];
|
|
5027
|
+
const askFile = deps.askFile === undefined ? undefined : validateAskFile(deps.askFile);
|
|
5028
|
+
const inheritedRootIntent = deps.parentDocument === undefined ? null : parseRootIntent(deps.parentDocument);
|
|
5029
|
+
const rootIntent = validateRootIntent(deps.supersessionRootIntent ?? inheritedRootIntent ?? deps.rootIntent ?? goalSummary(ask));
|
|
5030
|
+
const document = [
|
|
5031
|
+
deps.goalTitle ?? goalSummary(ask),
|
|
5032
|
+
`- GoalId: ${goalId}`,
|
|
5033
|
+
`- RootIntent: ${rootIntent}`,
|
|
5034
|
+
`- GoalType: ${goalType}`,
|
|
5035
|
+
...(askFile === undefined ? [] : [`- AskFile: ${askFile}`]),
|
|
5036
|
+
'',
|
|
5037
|
+
'## PROBLEM',
|
|
5038
|
+
`- 왜: ${enhancement.complication ?? goalSummary(ask)}`,
|
|
5039
|
+
'',
|
|
5040
|
+
'## WHAT TO BUILD',
|
|
5041
|
+
`- 무엇: ${enhancement.situation ?? goalSummary(ask)}`,
|
|
5042
|
+
ORIGINAL_ASK_MARKER,
|
|
5043
|
+
fenceFor(ask), ask, fenceFor(ask),
|
|
5044
|
+
'',
|
|
5045
|
+
'## ACCEPTANCE CRITERIA',
|
|
5046
|
+
...enhancement.checklist.map(requestedCriterionLine),
|
|
5047
|
+
'',
|
|
5048
|
+
'## REQUIRED EVIDENCE',
|
|
5049
|
+
'- [requested] Verify the decision signal and the verbatim ask.',
|
|
5050
|
+
'',
|
|
5051
|
+
...(goalType === 'implement' ? ['## TRACED PATHS', ...(targetPaths.length ? targetPaths.map((path) => `- ${path}`) : ['- Target paths: not declared in the ask.']), ''] : []),
|
|
5052
|
+
'## SCOPE BOUNDARY',
|
|
5053
|
+
`- 대상 경로: ${targetPaths.length ? targetPaths.join(' · ') : 'not declared'}`,
|
|
5054
|
+
`- 칸 id: ${checklistIds.length ? checklistIds.join(' · ') : 'not declared'}`,
|
|
5055
|
+
'',
|
|
5056
|
+
'## 답하지 못하는 것',
|
|
5057
|
+
'- No persistent grounding was performed in lite authoring.',
|
|
5058
|
+
'',
|
|
5059
|
+
'## 불변식',
|
|
5060
|
+
'- No persistent grounding was performed in lite authoring.',
|
|
5061
|
+
'',
|
|
5062
|
+
'## 판정 신호',
|
|
5063
|
+
...falsifiableSignal,
|
|
5064
|
+
].join('\n');
|
|
5065
|
+
return { document, facts: null, grounded: false, authorRunId };
|
|
5066
|
+
}
|
|
5067
|
+
|
|
4992
5068
|
observeGoalAuthor('goal-author', 'constant-instruction-placement', {
|
|
4993
5069
|
authorRunId,
|
|
4994
5070
|
lines: CONSTANT_DOCUMENT_INSTRUCTION_POLICY.length,
|
|
@@ -11,9 +11,13 @@ import {
|
|
|
11
11
|
writePendingQuestion,
|
|
12
12
|
} from '../ask-user-question/pending-questions.js';
|
|
13
13
|
import { debug } from '../debug/log.js';
|
|
14
|
+
import { writeGoalDocumentAtomic } from './goal-document-write.js';
|
|
14
15
|
import {
|
|
15
16
|
applyClarificationReply,
|
|
16
17
|
parseGoalDocumentClarifications,
|
|
18
|
+
selfAnswerGoalDocumentClarifications,
|
|
19
|
+
type GoalClarificationSelfResolver,
|
|
20
|
+
type GoalClarificationSelfAnswerPath,
|
|
17
21
|
} from './goal-author-clarification.js';
|
|
18
22
|
import type { HitlDelivery } from '../hitl/types.js';
|
|
19
23
|
|
|
@@ -49,6 +53,8 @@ export interface GoalClarificationEscalationInput {
|
|
|
49
53
|
resolveOriginDelivery?: (sessionId: string) => HitlDelivery | undefined;
|
|
50
54
|
/** Non-blocking terminal fallback for a non-TUI delivery with no installed resolver. */
|
|
51
55
|
fallback?: (message: string) => void;
|
|
56
|
+
selfResolveClarification?: GoalClarificationSelfResolver;
|
|
57
|
+
selfAnswerPath?: GoalClarificationSelfAnswerPath;
|
|
52
58
|
/** Fail-soft pending persistence for terminal fallbacks; injectable for focused tests. */
|
|
53
59
|
pendingQuestionPersistence?: {
|
|
54
60
|
create: typeof createPendingQuestion;
|
|
@@ -404,6 +410,7 @@ export async function escalateGoalDocumentClarifications(
|
|
|
404
410
|
const dispatchDelivery = delivery ?? 'modal';
|
|
405
411
|
const recordedDelivery = input.resolvedDelivery ?? dispatchDelivery;
|
|
406
412
|
let answersWritten = 0;
|
|
413
|
+
let selfAnswered = 0;
|
|
407
414
|
let questionIds: string[] = [];
|
|
408
415
|
const observeEscalation = (event: GoalClarificationObservationEvent, data: Record<string, unknown>): void => {
|
|
409
416
|
writeEscalationObservation(event, { ...data, deliverySource, answersWritten, questionIds });
|
|
@@ -430,6 +437,35 @@ export async function escalateGoalDocumentClarifications(
|
|
|
430
437
|
return result;
|
|
431
438
|
}
|
|
432
439
|
|
|
440
|
+
if (input.selfAnswerPath) {
|
|
441
|
+
try {
|
|
442
|
+
const resolution = await selfAnswerGoalDocumentClarifications(document, {
|
|
443
|
+
path: input.selfAnswerPath,
|
|
444
|
+
ask: document,
|
|
445
|
+
resolver: input.selfResolveClarification,
|
|
446
|
+
});
|
|
447
|
+
if (resolution.newlyAnswered > 0) {
|
|
448
|
+
writeGoalDocumentAtomic(input.goalFile, resolution.document);
|
|
449
|
+
answersWritten += resolution.newlyAnswered;
|
|
450
|
+
selfAnswered += resolution.newlyAnswered;
|
|
451
|
+
document = resolution.document;
|
|
452
|
+
clarifications = parseGoalDocumentClarifications(document);
|
|
453
|
+
unanswered = clarifications.filter((clarification) => !clarification.answered);
|
|
454
|
+
deliveryIds = mapClarificationDeliveryIds(clarifications);
|
|
455
|
+
questionIds = [...new Set(unanswered.map((clarification) => clarification.questionId))];
|
|
456
|
+
}
|
|
457
|
+
if (unanswered.length === 0) {
|
|
458
|
+
const result = { unanswered: 0, escalated: 0, delivery: recordedDelivery, outcome: 'skipped' as const, answeredBy: 'agent' as const };
|
|
459
|
+
observeEscalation(result.outcome, { goalFile: input.goalFile, ...result, reason: 'self-answered' });
|
|
460
|
+
return result;
|
|
461
|
+
}
|
|
462
|
+
} catch (error) {
|
|
463
|
+
debug.log(GOAL_CLARIFICATION_ESCALATION_CATEGORY, 'self-answer-failed', {
|
|
464
|
+
path: input.selfAnswerPath, reason: error instanceof Error ? error.message : String(error),
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
433
469
|
const dispatch = input.dispatch ?? dispatchAskUserQuestion;
|
|
434
470
|
const pendingQuestionPersistence = input.pendingQuestionPersistence ?? {
|
|
435
471
|
create: createPendingQuestion,
|
|
@@ -444,7 +480,7 @@ export async function escalateGoalDocumentClarifications(
|
|
|
444
480
|
// ⛔⭐⭐⭐ 여기서부터의 조기 반환·폴백은 이 «누적값»을 쓴다 — 'none' 을 하드코딩하면
|
|
445
481
|
// 앞 배치가 답했는데 뒤 배치가 취소·실패했을 때 「아무도 안 답했다」는 «거짓»이 된다
|
|
446
482
|
// (무인 리뷰 R2 must-fix · 실측 형태: 관측이 거짓말한다).
|
|
447
|
-
const seenProvenance = { explicit: new Set<'human' | 'agent'>(), unmarkedAnswer: false };
|
|
483
|
+
const seenProvenance = { explicit: new Set<'human' | 'agent'>(selfAnswered > 0 ? ['agent' as const] : []), unmarkedAnswer: false };
|
|
448
484
|
const aggregate = (): GoalClarificationAnsweredBy => {
|
|
449
485
|
const { explicit, unmarkedAnswer } = seenProvenance;
|
|
450
486
|
// ⭐ 표시 없는 답도 «한 출처»로 센다 — legacy 경로는 사람이다(레포 안 리졸버는 이제 전부 명시한다).
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Atomic goal-document replacement for self-answered clarifications (POD-SELF-ANSWER). */
|
|
2
|
+
import { chmodSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
export interface GoalDocumentWriteIo {
|
|
5
|
+
writeFileSync: (path: string, data: string, options?: { mode?: number }) => void;
|
|
6
|
+
renameSync: (from: string, to: string) => void;
|
|
7
|
+
rmSync: (path: string, options: { force: boolean }) => void;
|
|
8
|
+
statSync: (path: string) => { mode: number };
|
|
9
|
+
chmodSync: (path: string, mode: number) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const realIo: GoalDocumentWriteIo = { writeFileSync, renameSync, rmSync, statSync, chmodSync };
|
|
13
|
+
|
|
14
|
+
/** Replace a goal document atomically (temp file next to it + rename) so a failed write never leaves a torn goal.
|
|
15
|
+
* The existing file's permission bits are kept, so a 0600 goal stays 0600 after replacement. */
|
|
16
|
+
export function writeGoalDocumentAtomic(path: string, document: string, io: GoalDocumentWriteIo = realIo): void {
|
|
17
|
+
let mode: number | undefined;
|
|
18
|
+
try { mode = io.statSync(path).mode & 0o777; } catch { mode = undefined; }
|
|
19
|
+
const temporary = `${path}.self-answer-${process.pid}-${Date.now()}.tmp`;
|
|
20
|
+
try {
|
|
21
|
+
io.writeFileSync(temporary, document, mode === undefined ? undefined : { mode });
|
|
22
|
+
if (mode !== undefined) io.chmodSync(temporary, mode);
|
|
23
|
+
io.renameSync(temporary, path);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
try { io.rmSync(temporary, { force: true }); } catch { /* best effort */ }
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -68,6 +68,10 @@ function liveHealRuns(root: string): Set<string> {
|
|
|
68
68
|
/** A test process must never start the real heal loop — it spawns external grounding (10-05 load 150 incident). */
|
|
69
69
|
export async function startHealLoop(file: string, options: HealLoopOptions, env: NodeJS.ProcessEnv = process.env,
|
|
70
70
|
run: StartHealLoop = async (graph, args) => (await import('../graph-runner/runner.js')).runGraph(graph, args)): Promise<unknown> {
|
|
71
|
+
if (env.ELANOUS_POD_NAME) {
|
|
72
|
+
debug.log('heal.intake', 'skipped-in-pod', { ref: options.input.failureEvent.ref });
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
71
75
|
if (env.NODE_ENV === 'test' || env.ELANOUS_TEST_HOME) {
|
|
72
76
|
debug.log('heal.intake', 'loop-start-skipped-test', { runId: options.runId });
|
|
73
77
|
return undefined;
|
|
@@ -30,6 +30,8 @@ export type HostRegateDeps = {
|
|
|
30
30
|
acquire?: (repoRoot: string) => Promise<() => void>;
|
|
31
31
|
log?: (event: 'passed' | 'failed' | 'unmeasured' | 'base-raced', data: Record<string, unknown>) => void;
|
|
32
32
|
freezeRoot?: string;
|
|
33
|
+
/** Test seam for the operational freeze authority; production always uses the host root. */
|
|
34
|
+
prodFreezeRoot?: string;
|
|
33
35
|
runExposeGate?: typeof runExposeGate;
|
|
34
36
|
syncMergedChecklist?: typeof import('../release-loop/merged-pr-checklist.js').syncMergedPrChecklist;
|
|
35
37
|
};
|
|
@@ -241,7 +243,7 @@ export async function runHostRegate(input: HostRegateInput, deps: HostRegateDeps
|
|
|
241
243
|
// --match-head-commit pins the head. gh has no base pin, so the base was re-read just
|
|
242
244
|
// above; the seconds between that read and the merge are checked after the fact below.
|
|
243
245
|
if (input.verifyOnly) return result('passed');
|
|
244
|
-
const landing = admitLandingMerge({ prNumber: input.prNumber, headCommit: input.headCommit, repoRoot: input.repoRoot, ...(input.goalFile ? { goalFile: input.goalFile } : {}) }, deps.freezeRoot, {}, input.resumed ? undefined : { prNumber: input.prNumber, repoRoot: input.repoRoot, headCommit: input.headCommit });
|
|
246
|
+
const landing = admitLandingMerge({ prNumber: input.prNumber, headCommit: input.headCommit, repoRoot: input.repoRoot, ...(input.goalFile ? { goalFile: input.goalFile } : {}) }, deps.freezeRoot, {}, input.resumed ? undefined : { prNumber: input.prNumber, repoRoot: input.repoRoot, headCommit: input.headCommit }, { prodFreezeRoot: deps.prodFreezeRoot });
|
|
245
247
|
if (landing.kind !== 'merge') {
|
|
246
248
|
debug.log('harness.merge', 'frozen', { pr: input.prNumber, ...(landing.kind === 'held' ? { reason: landing.freeze.reason, until: landing.freeze.until } : { resumedElsewhere: true }) });
|
|
247
249
|
return { passed: true, failures: [], os: process.platform, status: 'frozen' };
|
|
@@ -34,6 +34,7 @@ import { judgePredictionAccuracy } from './judge-prediction-accuracy.js';
|
|
|
34
34
|
import { measureReviewFindingRecurrence, type ReviewFindingRecurrence } from './review-finding-recurrence.js';
|
|
35
35
|
import { createHash } from 'node:crypto';
|
|
36
36
|
import { appendFileSync, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync } from 'node:fs';
|
|
37
|
+
import { writeGoalDocumentAtomic } from './goal-document-write.js';
|
|
37
38
|
import { hostname, loadavg } from 'node:os';
|
|
38
39
|
import { resolveHostId } from '../platform/host-id.js';
|
|
39
40
|
import { resolveInstanceName } from '../instance-identity.js';
|
|
@@ -242,7 +243,7 @@ export type { QuotaAccountAvailabilityEvidence };
|
|
|
242
243
|
import { decideReworkSalvage, type ReworkSalvageEvidence } from './rework-salvage.js';
|
|
243
244
|
import { type SupervisorGoalFillEvidence, fillUnverifiableGoalSlots, type SupervisorGoalFillResult } from './goal-supervisor-fill.js';
|
|
244
245
|
import { escalateGoalDocumentClarifications, recordSupervisorPlanRevision, type GoalClarificationEscalationResult, type SupervisorPlanRevisionRelaxation, type SupervisorPlanRevisionResult } from './goal-clarification-escalation.js';
|
|
245
|
-
import { parseGoalDocumentClarifications } from './goal-author-clarification.js';
|
|
246
|
+
import { parseGoalDocumentClarifications, selfAnswerGoalDocumentClarifications, type GoalClarificationSelfResolver } from './goal-author-clarification.js';
|
|
246
247
|
import {
|
|
247
248
|
createFileAskUserQuestionResolver,
|
|
248
249
|
type AskUserQuestionDispatchContext,
|
|
@@ -1154,6 +1155,8 @@ export interface SelfImplementSeams {
|
|
|
1154
1155
|
/** ⭐ 「이 표면에 답할 사람이 있나」. 기본은 실제 stdin 의 TTY 여부다 —
|
|
1155
1156
|
* ⛔ 실물에 매이면 테스트가 «환경»을 검사하게 되므로 주입 가능하게 둔다. */
|
|
1156
1157
|
stdinIsInteractive?: () => boolean;
|
|
1158
|
+
/** Optional author self-answer seam for non-TTY Pods; never installs a terminal HITL resolver. */
|
|
1159
|
+
selfResolveClarification?: GoalClarificationSelfResolver;
|
|
1157
1160
|
}
|
|
1158
1161
|
|
|
1159
1162
|
/** onProgress seam 이 통지하는 단계(관측·UX 라벨). */
|
|
@@ -5364,6 +5367,7 @@ async function runSelfImplementInner(
|
|
|
5364
5367
|
? { resolvedDelivery: installDecision.delivery }
|
|
5365
5368
|
: {}),
|
|
5366
5369
|
...(s.escalateGoalClarifications ? { dispatch: s.escalateGoalClarifications } : {}),
|
|
5370
|
+
...(!stdinIsInteractive && !clarificationResolverLease.installed ? { selfAnswerPath: 'ask' as const, selfResolveClarification: s.selfResolveClarification } : {}),
|
|
5367
5371
|
fallback: (message) => process.stdout.write(`${message}\n`),
|
|
5368
5372
|
});
|
|
5369
5373
|
} finally {
|
|
@@ -6175,6 +6179,20 @@ async function runSelfImplementInner(
|
|
|
6175
6179
|
if (relaxation) pendingPlanRevision = { ...pendingPlanRevision!, relaxation };
|
|
6176
6180
|
if (opts.goalFile) {
|
|
6177
6181
|
try {
|
|
6182
|
+
if (!(s.stdinIsInteractive ?? (() => Boolean(process.stdin.isTTY && process.stdout.isTTY)))()) {
|
|
6183
|
+
try {
|
|
6184
|
+
const source = readFileSync(opts.goalFile, 'utf8');
|
|
6185
|
+
// Always run the shared self-answer so every rework pass leaves one observation, even with nothing pending.
|
|
6186
|
+
const resolved = await selfAnswerGoalDocumentClarifications(source, {
|
|
6187
|
+
path: 'rework', ask: source, resolver: s.selfResolveClarification,
|
|
6188
|
+
});
|
|
6189
|
+
if (resolved.newlyAnswered > 0) writeGoalDocumentAtomic(opts.goalFile, resolved.document);
|
|
6190
|
+
} catch (error) {
|
|
6191
|
+
debug.log('self-implement.clarification-escalation', 'self-answer-failed', {
|
|
6192
|
+
path: 'rework', reason: error instanceof Error ? error.message : String(error),
|
|
6193
|
+
});
|
|
6194
|
+
}
|
|
6195
|
+
}
|
|
6178
6196
|
const recorded: SupervisorPlanRevisionResult = recordSupervisorPlanRevision(opts.goalFile, { reason: parsedDecision.reason, round, ...(relaxation ? { relaxation } : {}) });
|
|
6179
6197
|
pendingPlanRevision = { ...pendingPlanRevision!, ...(relaxation ? { relaxation } : {}), application: {
|
|
6180
6198
|
status: recorded.status === 'applied' || recorded.status === 'already-applied' ? recorded.status : 'failed',
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
export type NextActionKind = 'review' | 'land' | 'retry' | 'green' | 'decision';
|
|
2
|
+
export interface NextAction {
|
|
3
|
+
kind: NextActionKind;
|
|
4
|
+
taskId: string;
|
|
5
|
+
rationale: string;
|
|
6
|
+
pr: number;
|
|
7
|
+
runId: string;
|
|
8
|
+
original: string;
|
|
9
|
+
checklistId: string;
|
|
10
|
+
history?: string[];
|
|
11
|
+
mustFix?: string[];
|
|
12
|
+
cwd?: string;
|
|
13
|
+
risk?: 'money' | 'security' | 'irreversible-publication';
|
|
14
|
+
}
|
|
15
|
+
export interface ActionDeps {
|
|
16
|
+
command(args: string[]): Promise<{ status: number; stdout: string; stderr?: string }>;
|
|
17
|
+
mode?: string;
|
|
18
|
+
landingsToday?: number;
|
|
19
|
+
landingDay?: string;
|
|
20
|
+
saveState?(): void;
|
|
21
|
+
dailyLimit?: number;
|
|
22
|
+
failures?: number;
|
|
23
|
+
failureCounts?: Record<string, number>;
|
|
24
|
+
/** 하루 착지 칸을 잠금 안에서 먼저 확보한다 — 다른 프로세스와 같은 잔여 칸을 두 번 쓰지 않게. 없으면 메모리 셈. */
|
|
25
|
+
reserveLanding?(day: string, limit: number): boolean;
|
|
26
|
+
/** 병합이 확인되지 않으면 확보한 칸을 돌려준다. */
|
|
27
|
+
releaseLanding?(day: string): void;
|
|
28
|
+
/** 실패 한 번을 잠금 안에서 디스크에 더한다(다른 프로세스의 실패를 덮지 않게) · 더한 뒤의 값을 돌려준다. */
|
|
29
|
+
incrementFailure?(key: string): number;
|
|
30
|
+
observe(event: 'task-agent.action', data: { taskId: string; kind: NextActionKind; result: string; reason: string }): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function executeNextAction(action: NextAction, deps: ActionDeps): Promise<string> {
|
|
34
|
+
const record = (result: string, reason: string) => {
|
|
35
|
+
if (result === 'failed' && (deps.failureCounts || deps.incrementFailure)) {
|
|
36
|
+
const key = JSON.stringify([action.taskId, action.kind]);
|
|
37
|
+
const next = deps.incrementFailure ? deps.incrementFailure(key) : (deps.failureCounts![key] ?? 0) + 1;
|
|
38
|
+
if (deps.failureCounts) deps.failureCounts[key] = next;
|
|
39
|
+
if (!deps.incrementFailure) deps.saveState?.();
|
|
40
|
+
}
|
|
41
|
+
deps.observe('task-agent.action', { taskId: action.taskId, kind: action.kind, result, reason });
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
if (deps.mode !== 'live') return record('shadow', `would ${action.kind}: ${action.rationale}`);
|
|
45
|
+
const card = async (reason: string) => {
|
|
46
|
+
const evidence = JSON.stringify({ runId: action.runId, pr: action.pr, mustFix: action.mustFix ?? [], history: action.history ?? [], selected: action.kind, reason });
|
|
47
|
+
const result = await deps.command(['decisions', 'raise', '--title', `${action.taskId}: ${reason}`, '--category', action.risk === 'money' ? 'money' : action.risk === 'security' ? 'security' : action.risk === 'irreversible-publication' ? 'publish' : 'other', '--s', `Task ${action.taskId} needs a decision.`, '--c', reason, '--ref', evidence, '--q', 'Which action should be taken?', '--option', 'h=Keep on hold:no automatic execution', '--option', 'p=Approve action:execute after approval', '--skip-recommend', 'requires human decision', '--no-xcheck', 'automated escalation']);
|
|
48
|
+
return record(result.status === 0 ? 'decision' : 'failed', reason);
|
|
49
|
+
};
|
|
50
|
+
if (action.risk) return card(`risk: ${action.risk}`);
|
|
51
|
+
if ((deps.failureCounts?.[JSON.stringify([action.taskId, action.kind])] ?? deps.failures ?? 0) >= 2) return card('same task/action failed twice');
|
|
52
|
+
if (action.kind === 'land') {
|
|
53
|
+
if (!action.cwd) return card('landing worktree unknown');
|
|
54
|
+
const view = await deps.command(['gh', 'pr', 'view', String(action.pr), '--json', 'labels']);
|
|
55
|
+
if (view.status !== 0) return card('PR labels unavailable');
|
|
56
|
+
let labels: unknown;
|
|
57
|
+
try { labels = (JSON.parse(view.stdout) as { labels?: unknown }).labels; }
|
|
58
|
+
catch { return card('PR labels unavailable'); }
|
|
59
|
+
if (!Array.isArray(labels) || !labels.every(label => label && typeof label === 'object' && typeof label.name === 'string')) return card('PR labels unavailable');
|
|
60
|
+
if (labels.some(label => label.name === 'elanous:release-path')) return card('release-path');
|
|
61
|
+
const day = new Date().toLocaleDateString('en-CA', { timeZone: 'Asia/Seoul' });
|
|
62
|
+
if (deps.landingDay && deps.landingDay !== day) deps.landingsToday = 0;
|
|
63
|
+
deps.landingDay = day;
|
|
64
|
+
const limit = deps.dailyLimit ?? 5;
|
|
65
|
+
if (deps.reserveLanding ? !deps.reserveLanding(day, limit) : (deps.landingsToday ?? 0) >= limit) return card('daily landing limit');
|
|
66
|
+
}
|
|
67
|
+
if (action.kind === 'decision') return card(action.rationale);
|
|
68
|
+
if (action.kind === 'green') return card('green requires a verified landing in this execution');
|
|
69
|
+
if (action.kind === 'retry' && !action.mustFix?.length) return card('must-fix missing; cannot narrow retry');
|
|
70
|
+
if (action.kind === 'review') {
|
|
71
|
+
const review = await deps.command(['self', 'review', String(action.pr), '--json']);
|
|
72
|
+
if (review.status !== 0) return record('failed', 'review command failed');
|
|
73
|
+
let parsed: { verdict?: string; mustFix?: string[]; reviewed?: boolean };
|
|
74
|
+
try {
|
|
75
|
+
const payload: unknown = JSON.parse(review.stdout);
|
|
76
|
+
const item = Array.isArray(payload) ? payload.find((entry: unknown) => entry && typeof entry === 'object' && 'pr' in entry && Number(entry.pr) === action.pr) : payload;
|
|
77
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) return record('failed', 'invalid review JSON');
|
|
78
|
+
parsed = item as typeof parsed;
|
|
79
|
+
} catch { return record('failed', 'invalid review JSON'); }
|
|
80
|
+
if (parsed.reviewed !== true) return record('failed', 'review was not performed');
|
|
81
|
+
if (parsed.verdict === 'pass') { record('done', 'review pass'); return executeNextAction({ ...action, kind: 'land', history: [...(action.history ?? []), 'review pass'] }, deps); }
|
|
82
|
+
if (parsed.verdict === 'fail' && Array.isArray(parsed.mustFix) && parsed.mustFix.length > 0 && parsed.mustFix.every(fix => typeof fix === 'string' && fix.trim())) { record('done', 'review fail'); return executeNextAction({ ...action, kind: 'retry', mustFix: parsed.mustFix, history: [...(action.history ?? []), 'review fail'] }, deps); }
|
|
83
|
+
return card('review verdict or must-fix unavailable');
|
|
84
|
+
}
|
|
85
|
+
// 여기 닿는 종류는 land · retry 둘뿐이다(green·decision·review 는 위에서 끝난다).
|
|
86
|
+
const args = action.kind === 'land' ? ['pr', 'land', '--cwd', action.cwd!]
|
|
87
|
+
: ['harness', 'say', `${action.original}\n\nMust-fix:\n${(action.mustFix ?? []).join('\n')}\n\nPrevious run: ${action.runId}`];
|
|
88
|
+
const day = deps.landingDay ?? new Date().toLocaleDateString('en-CA', { timeZone: 'Asia/Seoul' });
|
|
89
|
+
const outcome = await deps.command(args);
|
|
90
|
+
if (outcome.status !== 0) {
|
|
91
|
+
if (action.kind === 'land') deps.releaseLanding?.(day);
|
|
92
|
+
return record('failed', `${action.kind} command failed: ${outcome.stderr ?? ''}`);
|
|
93
|
+
}
|
|
94
|
+
if (action.kind === 'land') {
|
|
95
|
+
const merged = await deps.command(['gh', 'pr', 'view', String(action.pr), '--json', 'state,mergeCommit']);
|
|
96
|
+
let sha: string | undefined;
|
|
97
|
+
let notMerged = false;
|
|
98
|
+
try {
|
|
99
|
+
const value = JSON.parse(merged.stdout) as { state?: string; mergeCommit?: { oid?: string } };
|
|
100
|
+
if (merged.status === 0 && value.state === 'MERGED' && /^[a-f0-9]{40}$/i.test(value.mergeCommit?.oid ?? '')) sha = value.mergeCommit?.oid;
|
|
101
|
+
else if (merged.status === 0 && typeof value.state === 'string' && value.state !== 'MERGED') notMerged = true;
|
|
102
|
+
} catch { /* unknown merge state is never green */ }
|
|
103
|
+
if (!sha) {
|
|
104
|
+
// «열림»으로 확인됐을 때만 칸을 돌려준다. 상태를 못 읽었으면 병합됐을 수 있으니 칸은 쓴 것으로 둔다(상한을 넘지 않게).
|
|
105
|
+
if (notMerged) deps.releaseLanding?.(day);
|
|
106
|
+
return card('landing commit unknown; checklist remains unchanged');
|
|
107
|
+
}
|
|
108
|
+
record('done', action.rationale);
|
|
109
|
+
if (!deps.reserveLanding) deps.landingsToday = (deps.landingsToday ?? 0) + 1;
|
|
110
|
+
deps.saveState?.();
|
|
111
|
+
const green = await deps.command(['release', 'checklist', 'set', action.checklistId, '--status', 'green', '--evidence', `${action.pr}·${sha}`]);
|
|
112
|
+
// 체크리스트 실패는 이 과제·land 의 실패로 센다(두 번이면 다음엔 카드).
|
|
113
|
+
if (green.status !== 0) return record('failed', `green command failed: ${green.stderr ?? ''}`);
|
|
114
|
+
deps.observe('task-agent.action', { taskId: action.taskId, kind: 'green', result: 'done', reason: action.rationale });
|
|
115
|
+
return 'done';
|
|
116
|
+
}
|
|
117
|
+
return record('done', action.rationale);
|
|
118
|
+
}
|