@linimin/pi-letscook 0.1.37 → 0.1.40
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 +29 -2
- package/PUBLISHING.md +2 -0
- package/README.md +12 -14
- package/extensions/completion/index.ts +173 -233
- package/package.json +1 -1
- package/scripts/active-slice-contract-test.sh +48 -1
- package/scripts/canonical-evidence-artifact-test.sh +48 -1
- package/scripts/context-proposal-test.sh +576 -133
- package/scripts/observability-status-test.sh +3 -0
- package/scripts/refocus-test.sh +79 -31
- package/scripts/release-check.sh +45 -11
- package/scripts/smoke-test.sh +76 -1
|
@@ -467,7 +467,7 @@ type ActiveWorkflowProposalAssessment = {
|
|
|
467
467
|
action: "continue" | "refocus" | "unclear";
|
|
468
468
|
currentMissionAnchor: string;
|
|
469
469
|
proposal?: ContextProposal;
|
|
470
|
-
reason: "matching_mission" | "clear_refocus" | "
|
|
470
|
+
reason: "matching_mission" | "clear_refocus" | "missing_proposal" | "ambiguous_discussion";
|
|
471
471
|
};
|
|
472
472
|
|
|
473
473
|
type ExistingWorkflowChooserOptions = {
|
|
@@ -539,8 +539,10 @@ function maybeWriteTestSnapshot(targetPath: string | undefined, content: string)
|
|
|
539
539
|
}
|
|
540
540
|
|
|
541
541
|
const COOK_MAIN_CHAT_RERUN_GUIDANCE = "Discuss changes in the main chat and rerun /cook.";
|
|
542
|
+
const COOK_INLINE_ARG_REJECTION_MESSAGE =
|
|
543
|
+
"Inline /cook arguments are no longer supported. Clarify the mission in the main chat and rerun bare /cook.";
|
|
542
544
|
const COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL =
|
|
543
|
-
"Bare /cook failed closed because recent discussion did not contain a clear
|
|
545
|
+
"Bare /cook failed closed because recent discussion did not contain a clear execution-ready Mission/Scope/Constraints/Acceptance proposal for concrete repo changes. Clarify the concrete repo changes in the main chat and rerun bare /cook.";
|
|
544
546
|
|
|
545
547
|
function buildCookCancellationMessage(prefix: string): string {
|
|
546
548
|
return `${prefix}. ${COOK_MAIN_CHAT_RERUN_GUIDANCE}`;
|
|
@@ -753,6 +755,94 @@ function matchContextProposalRoutingHint(
|
|
|
753
755
|
return label === "tasktype" ? { field: "taskType", value } : { field: "evaluationProfile", value };
|
|
754
756
|
}
|
|
755
757
|
|
|
758
|
+
const CONTEXT_PROPOSAL_GENERIC_PLANNING_MISSION_REGEX =
|
|
759
|
+
/(?:\b(?:start(?:ing)?|begin|continue|continu(?:e|ing)|resume|implement(?:ing)?|execute|execut(?:e|ing)|carry out|work on|ship|build(?:ing)?)\b.*\b(?:this|that|the|current|latest)\s+(?:plan|proposal|spec(?:ification)?|design(?: doc(?:ument)?)?|migration plan)\b|(?:開始|著手|繼續|继续|恢復|恢复)?(?:實作|实现|執行|执行|落地|完成)(?:這個|这个|此|該|该)?(?:方案|計畫|计划|提案|規劃|规划|設計|设计))/iu;
|
|
760
|
+
const CONTEXT_PROPOSAL_PLANNING_ONLY_DELIVERABLE_REGEX =
|
|
761
|
+
/(?:\b(?:write|draft|prepare|create|produce|share|deliver|document|review)\b.*\b(?:plan|spec(?:ification)?|design(?: doc(?:ument)?)?|migration plan|proposal)\b|(?:撰寫|撰写|編寫|编写|起草|準備|准备|產出|产出|整理|分享|交付|審查|审查).*(?:計畫|计划|規格|规格|設計文件|设计文档|提案|方案))/iu;
|
|
762
|
+
const CONTEXT_PROPOSAL_DOCS_ONLY_SIGNAL_REGEX = /(?:\b(?:docs? only|documentation only)\b|(?:只改文件|僅文件|仅文件))/iu;
|
|
763
|
+
const CONTEXT_PROPOSAL_NO_IMPLEMENTATION_SIGNAL_REGEX =
|
|
764
|
+
/(?:\b(?:no code(?: changes?)?|without code(?: changes?)?|do not implement|don't implement|planning only|proposal only|spec only|design[- ]doc only|no runtime changes?)\b|(?:不改(?:動)?代碼|不改代码|不要實作|不要实现|只規劃|只规划|僅規劃|仅规划|不改(?:動)?執行|不改运行))/iu;
|
|
765
|
+
const CONTEXT_PROPOSAL_IMPLEMENTATION_SOURCE_REGEX =
|
|
766
|
+
/(?:\b(?:normalize|fix|update|add|remove|restore|refactor|ship|support|wire|route|rewrite|replace|preserve|filter|separate|refresh|reroute|suppress|align|convert|reconcile|repair|correct|implement|build|land|block|allow|keep|edit(?:ing)?|document(?:ing)?|writ(?:e|ing))\b|(?:修正|修復|修复|更新|新增|移除|恢復|恢复|重構|重构|調整|调整|正規化|规范化|規範化|过滤|過濾|分離|分离|刷新|替換|替换|抑制|對齊|对齐|實作|实现|落地|修補|修补|阻止|允許|允许|轉換|转换|保留|保持))/iu;
|
|
767
|
+
|
|
768
|
+
function contextProposalBodyTexts(proposal: Pick<ContextProposal, "scope" | "constraints" | "acceptance">): string[] {
|
|
769
|
+
return [...proposal.scope, ...proposal.constraints, ...proposal.acceptance];
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function isGenericPlanningMissionAnchor(text: string): boolean {
|
|
773
|
+
const normalized = normalizeMissionAnchorText(text);
|
|
774
|
+
if (!normalized) return false;
|
|
775
|
+
return CONTEXT_PROPOSAL_GENERIC_PLANNING_MISSION_REGEX.test(normalized);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function hasExplicitPlanningOnlyDeliverable(texts: string[]): boolean {
|
|
779
|
+
return texts.some((text) => CONTEXT_PROPOSAL_PLANNING_ONLY_DELIVERABLE_REGEX.test(normalizeProposalLine(text)));
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function normalizeImplementationMissionSourceText(text: string): string {
|
|
783
|
+
const normalized = normalizeProposalLine(text);
|
|
784
|
+
if (!normalized) return "";
|
|
785
|
+
return normalized
|
|
786
|
+
.replace(new RegExp(`${CONTEXT_PROPOSAL_DOCS_ONLY_SIGNAL_REGEX.source}[\\s::;,/\\-]*`, "giu"), " ")
|
|
787
|
+
.replace(/\s+([,.;:!?])/g, "$1")
|
|
788
|
+
.replace(/\s+/g, " ")
|
|
789
|
+
.trim();
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function hasClearNoImplementationSignal(texts: string[]): boolean {
|
|
793
|
+
return texts.some((text) => CONTEXT_PROPOSAL_NO_IMPLEMENTATION_SIGNAL_REGEX.test(normalizeProposalLine(text)));
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function implementationMissionSourceCandidateText(text: string): string | undefined {
|
|
797
|
+
const normalized = normalizeImplementationMissionSourceText(text);
|
|
798
|
+
if (!normalized) return undefined;
|
|
799
|
+
if (hasExplicitPlanningOnlyDeliverable([normalized])) return undefined;
|
|
800
|
+
if (hasClearNoImplementationSignal([normalized])) return undefined;
|
|
801
|
+
if (!CONTEXT_PROPOSAL_IMPLEMENTATION_SOURCE_REGEX.test(normalized)) return undefined;
|
|
802
|
+
return normalized;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function pickImplementationMissionSource(proposal: Pick<ContextProposal, "scope" | "constraints" | "acceptance">): string | undefined {
|
|
806
|
+
for (const item of proposal.scope) {
|
|
807
|
+
const candidate = implementationMissionSourceCandidateText(item);
|
|
808
|
+
if (candidate) return candidate;
|
|
809
|
+
}
|
|
810
|
+
for (const item of proposal.acceptance) {
|
|
811
|
+
const candidate = implementationMissionSourceCandidateText(item);
|
|
812
|
+
if (candidate) return candidate;
|
|
813
|
+
}
|
|
814
|
+
return undefined;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
function hasPlanningArtifactOnlyContext(
|
|
818
|
+
proposal: Pick<ContextProposal, "mission" | "scope" | "constraints" | "acceptance">,
|
|
819
|
+
): boolean {
|
|
820
|
+
const texts = [proposal.mission, ...contextProposalBodyTexts(proposal)];
|
|
821
|
+
if (!hasExplicitPlanningOnlyDeliverable(texts)) return false;
|
|
822
|
+
return !pickImplementationMissionSource(proposal);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function finalizeContextProposal(proposal: ContextProposal, projectName: string): ContextProposal | undefined {
|
|
826
|
+
if (hasPlanningArtifactOnlyContext(proposal)) return undefined;
|
|
827
|
+
if (!isGenericPlanningMissionAnchor(proposal.mission)) return proposal;
|
|
828
|
+
const missionSource = pickImplementationMissionSource(proposal);
|
|
829
|
+
if (!missionSource) return undefined;
|
|
830
|
+
const nextMission = assessMissionAnchor(missionSource, projectName).derived;
|
|
831
|
+
const normalizedNextMission = normalizeMissionAnchorText(nextMission);
|
|
832
|
+
if (!normalizedNextMission || isWeakMissionAnchor(normalizedNextMission)) return undefined;
|
|
833
|
+
if (missionAnchorsStrictlyEquivalent(nextMission, proposal.mission)) return proposal;
|
|
834
|
+
return {
|
|
835
|
+
...proposal,
|
|
836
|
+
mission: nextMission,
|
|
837
|
+
goalText: buildContextProposalGoalText({
|
|
838
|
+
mission: nextMission,
|
|
839
|
+
scope: proposal.scope,
|
|
840
|
+
constraints: proposal.constraints,
|
|
841
|
+
acceptance: proposal.acceptance,
|
|
842
|
+
}),
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
|
|
756
846
|
const MISSION_SCOPE_FILTER_STOPWORDS = new Set([
|
|
757
847
|
"a",
|
|
758
848
|
"an",
|
|
@@ -887,19 +977,14 @@ function shouldTreatBareActiveWorkflowProposalAsClearRefocus(proposal: ContextPr
|
|
|
887
977
|
);
|
|
888
978
|
}
|
|
889
979
|
|
|
890
|
-
function maybeWriteActiveWorkflowRoutingSnapshot(
|
|
891
|
-
assessment: ActiveWorkflowProposalAssessment,
|
|
892
|
-
options: { mode: "bare" | "explicit"; explicitGoal?: string },
|
|
893
|
-
): void {
|
|
980
|
+
function maybeWriteActiveWorkflowRoutingSnapshot(assessment: ActiveWorkflowProposalAssessment): void {
|
|
894
981
|
const snapshotPath = completionTestActiveWorkflowRoutingSnapshotPath();
|
|
895
982
|
if (!snapshotPath) return;
|
|
896
983
|
maybeWriteTestSnapshot(
|
|
897
984
|
snapshotPath,
|
|
898
985
|
`${JSON.stringify(
|
|
899
986
|
{
|
|
900
|
-
mode:
|
|
901
|
-
explicitGoalProvided: Boolean(options.explicitGoal),
|
|
902
|
-
explicitGoal: options.explicitGoal ?? null,
|
|
987
|
+
mode: "bare",
|
|
903
988
|
action: assessment.action,
|
|
904
989
|
reason: assessment.reason,
|
|
905
990
|
currentMissionAnchor: assessment.currentMissionAnchor,
|
|
@@ -928,7 +1013,6 @@ const CONTEXT_PROPOSAL_ANALYST_SYSTEM_PROMPT = [
|
|
|
928
1013
|
"risks must contain concrete failure modes or regressions that the later workflow should keep in view.",
|
|
929
1014
|
"task_type and evaluation_profile should be candidate routing hints only; reuse the existing completion vocabulary when it clearly fits instead of inventing new schema names.",
|
|
930
1015
|
"possible_noise should list discussion points that look stale, weakly related, or unsafe to promote into scope.",
|
|
931
|
-
"When an explicit goal is provided, keep the mission anchored to that goal instead of replacing it with a broader or different mission.",
|
|
932
1016
|
"When discussion is insufficient, prefer empty arrays and a low confidence value over invention.",
|
|
933
1017
|
].join(" ");
|
|
934
1018
|
|
|
@@ -948,12 +1032,9 @@ function collectRecentDiscussionEntries(ctx: { sessionManager: any }, limit = 8)
|
|
|
948
1032
|
let text = "";
|
|
949
1033
|
let role: RecentDiscussionEntry["role"] | undefined;
|
|
950
1034
|
const messageRole = asString(message.role);
|
|
951
|
-
if (messageRole === "user" || messageRole === "
|
|
1035
|
+
if (messageRole === "user" || messageRole === "custom") {
|
|
952
1036
|
text = extractTextFromMessageContent(message.content);
|
|
953
1037
|
role = messageRole;
|
|
954
|
-
} else if (messageRole === "branchSummary" || messageRole === "compactionSummary") {
|
|
955
|
-
text = asString(message.summary) ?? "";
|
|
956
|
-
role = "summary";
|
|
957
1038
|
}
|
|
958
1039
|
if (!text || !role) continue;
|
|
959
1040
|
const trimmed = text.trim();
|
|
@@ -983,11 +1064,7 @@ function extractJsonObjectFromText(text: string): string | undefined {
|
|
|
983
1064
|
return unfenced.slice(start, end + 1);
|
|
984
1065
|
}
|
|
985
1066
|
|
|
986
|
-
function parseContextProposalAnalystOutput(
|
|
987
|
-
raw: string,
|
|
988
|
-
projectName: string,
|
|
989
|
-
explicitGoal?: string,
|
|
990
|
-
): ContextProposal | undefined {
|
|
1067
|
+
function parseContextProposalAnalystOutput(raw: string, projectName: string): ContextProposal | undefined {
|
|
991
1068
|
const jsonText = extractJsonObjectFromText(raw);
|
|
992
1069
|
if (!jsonText) return undefined;
|
|
993
1070
|
let parsed: unknown;
|
|
@@ -997,8 +1074,7 @@ function parseContextProposalAnalystOutput(
|
|
|
997
1074
|
return undefined;
|
|
998
1075
|
}
|
|
999
1076
|
if (!isRecord(parsed)) return undefined;
|
|
1000
|
-
const
|
|
1001
|
-
const missionSource = explicit?.mission ?? explicitGoal ?? asString(parsed.mission);
|
|
1077
|
+
const missionSource = asString(parsed.mission);
|
|
1002
1078
|
if (!missionSource) return undefined;
|
|
1003
1079
|
const assessment = assessMissionAnchor(missionSource, projectName);
|
|
1004
1080
|
const normalizedMission = normalizeMissionAnchorText(missionSource);
|
|
@@ -1007,31 +1083,28 @@ function parseContextProposalAnalystOutput(
|
|
|
1007
1083
|
const scope = uniqueProposalItems(asStringArray(parsed.scope));
|
|
1008
1084
|
const constraints = uniqueProposalItems(asStringArray(parsed.constraints));
|
|
1009
1085
|
const acceptance = uniqueProposalItems(asStringArray(parsed.acceptance));
|
|
1010
|
-
const analysis =
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
possibleNoise: asStringArray(parsed.possible_noise ?? parsed.possibleNoise),
|
|
1019
|
-
hintTexts: [raw, mission, ...scope, ...constraints, ...acceptance],
|
|
1020
|
-
}),
|
|
1021
|
-
],
|
|
1022
|
-
[explicitGoal ?? "", raw, mission, ...scope, ...constraints, ...acceptance],
|
|
1023
|
-
);
|
|
1086
|
+
const analysis = buildContextProposalAnalysis({
|
|
1087
|
+
taskType: parsed.task_type ?? parsed.taskType,
|
|
1088
|
+
evaluationProfile: parsed.evaluation_profile ?? parsed.evaluationProfile,
|
|
1089
|
+
critique: asStringArray(parsed.critique),
|
|
1090
|
+
risks: asStringArray(parsed.risks ?? parsed.risk),
|
|
1091
|
+
possibleNoise: asStringArray(parsed.possible_noise ?? parsed.possibleNoise),
|
|
1092
|
+
hintTexts: [raw, mission, ...scope, ...constraints, ...acceptance],
|
|
1093
|
+
});
|
|
1024
1094
|
const goalText = buildContextProposalGoalText({ mission, scope, constraints, acceptance });
|
|
1025
|
-
return
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1095
|
+
return finalizeContextProposal(
|
|
1096
|
+
{
|
|
1097
|
+
mission,
|
|
1098
|
+
scope,
|
|
1099
|
+
constraints,
|
|
1100
|
+
acceptance,
|
|
1101
|
+
analysis,
|
|
1102
|
+
goalText,
|
|
1103
|
+
basisPreview: raw.replace(/\s+/g, " ").trim(),
|
|
1104
|
+
source: "analyst",
|
|
1105
|
+
},
|
|
1106
|
+
projectName,
|
|
1107
|
+
);
|
|
1035
1108
|
}
|
|
1036
1109
|
|
|
1037
1110
|
function contextProposalAnalystModelArg(model: unknown): string | undefined {
|
|
@@ -1041,17 +1114,9 @@ function contextProposalAnalystModelArg(model: unknown): string | undefined {
|
|
|
1041
1114
|
return provider && id ? `${provider}/${id}` : undefined;
|
|
1042
1115
|
}
|
|
1043
1116
|
|
|
1044
|
-
function buildContextProposalAnalystPrompt(projectName: string, recentEntries: RecentDiscussionEntry[]
|
|
1117
|
+
function buildContextProposalAnalystPrompt(projectName: string, recentEntries: RecentDiscussionEntry[]): string {
|
|
1045
1118
|
const discussion = serializeRecentDiscussionEntries(recentEntries);
|
|
1046
|
-
return [
|
|
1047
|
-
`Project: ${projectName}`,
|
|
1048
|
-
explicitGoal
|
|
1049
|
-
? `Explicit goal (keep this mission anchor):\n${explicitGoal}`
|
|
1050
|
-
: "Explicit goal: none provided; infer the current mission from the discussion.",
|
|
1051
|
-
"",
|
|
1052
|
-
"Recent discussion:",
|
|
1053
|
-
discussion,
|
|
1054
|
-
].join("\n");
|
|
1119
|
+
return [`Project: ${projectName}`, "Infer the current mission from the discussion.", "", "Recent discussion:", discussion].join("\n");
|
|
1055
1120
|
}
|
|
1056
1121
|
|
|
1057
1122
|
function contextProposalAnalystProgressLines(activity: LiveRoleActivity): string[] {
|
|
@@ -1080,14 +1145,13 @@ async function runContextProposalAnalystSubprocess(
|
|
|
1080
1145
|
ctx: { cwd: string; hasUI: boolean; ui: any; model?: any },
|
|
1081
1146
|
projectName: string,
|
|
1082
1147
|
recentEntries: RecentDiscussionEntry[],
|
|
1083
|
-
explicitGoal?: string,
|
|
1084
1148
|
): Promise<string | undefined> {
|
|
1085
1149
|
const modelArg = contextProposalAnalystModelArg(ctx.model);
|
|
1086
1150
|
if (!modelArg) return undefined;
|
|
1087
1151
|
const cwd = getCtxCwd(ctx);
|
|
1088
1152
|
const runCwd = findCompletionRoot(cwd) ?? findRepoRoot(cwd) ?? cwd;
|
|
1089
1153
|
const rootKey = completionRootKey(undefined, cwd);
|
|
1090
|
-
const prompt = buildContextProposalAnalystPrompt(projectName, recentEntries
|
|
1154
|
+
const prompt = buildContextProposalAnalystPrompt(projectName, recentEntries);
|
|
1091
1155
|
const systemPromptTemp = await writeTempFile("pi-cook-proposal-analyst-", CONTEXT_PROPOSAL_ANALYST_SYSTEM_PROMPT);
|
|
1092
1156
|
const analystRole = "cook-proposal-analyst";
|
|
1093
1157
|
const args: string[] = ["--mode", "json", "-p", "--no-session", "--append-system-prompt", systemPromptTemp.filePath, "--model", modelArg, prompt];
|
|
@@ -1189,18 +1253,17 @@ async function analyzeContextProposalWithAgent(
|
|
|
1189
1253
|
ctx: { cwd: string; hasUI: boolean; ui: any; model?: any; modelRegistry?: any },
|
|
1190
1254
|
projectName: string,
|
|
1191
1255
|
recentEntries: RecentDiscussionEntry[],
|
|
1192
|
-
explicitGoal?: string,
|
|
1193
1256
|
): Promise<ContextProposal | undefined> {
|
|
1194
1257
|
if (shouldDisableContextProposalAnalyst()) return undefined;
|
|
1195
1258
|
const testOutput = completionTestContextProposalAnalystOutput();
|
|
1196
1259
|
if (testOutput) {
|
|
1197
|
-
return parseContextProposalAnalystOutput(testOutput, projectName
|
|
1260
|
+
return parseContextProposalAnalystOutput(testOutput, projectName);
|
|
1198
1261
|
}
|
|
1199
1262
|
if (recentEntries.length === 0) return undefined;
|
|
1200
1263
|
try {
|
|
1201
|
-
const raw = await runContextProposalAnalystSubprocess(ctx, projectName, recentEntries
|
|
1264
|
+
const raw = await runContextProposalAnalystSubprocess(ctx, projectName, recentEntries);
|
|
1202
1265
|
if (!raw) return undefined;
|
|
1203
|
-
return parseContextProposalAnalystOutput(raw, projectName
|
|
1266
|
+
return parseContextProposalAnalystOutput(raw, projectName);
|
|
1204
1267
|
} catch (error) {
|
|
1205
1268
|
console.warn("[completion] context proposal analyst failed", error);
|
|
1206
1269
|
return undefined;
|
|
@@ -1562,16 +1625,19 @@ function parseContextProposal(text: string, projectName: string): ContextProposa
|
|
|
1562
1625
|
hintTexts: [cleaned, mission, ...scope, ...constraints, ...acceptance, ...critique, ...risks],
|
|
1563
1626
|
});
|
|
1564
1627
|
const goalText = buildContextProposalGoalText({ mission, scope, constraints, acceptance });
|
|
1565
|
-
return
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1628
|
+
return finalizeContextProposal(
|
|
1629
|
+
{
|
|
1630
|
+
mission,
|
|
1631
|
+
scope,
|
|
1632
|
+
constraints,
|
|
1633
|
+
acceptance,
|
|
1634
|
+
analysis,
|
|
1635
|
+
goalText,
|
|
1636
|
+
basisPreview,
|
|
1637
|
+
source: "session",
|
|
1638
|
+
},
|
|
1639
|
+
projectName,
|
|
1640
|
+
);
|
|
1575
1641
|
}
|
|
1576
1642
|
|
|
1577
1643
|
function hasStructuredContextProposalSignal(text: string): boolean {
|
|
@@ -1627,7 +1693,12 @@ function parseStrictStructuredSessionProposal(text: string, projectName: string)
|
|
|
1627
1693
|
|
|
1628
1694
|
const proposal = parseContextProposal(cleaned, projectName);
|
|
1629
1695
|
if (!proposal) return undefined;
|
|
1630
|
-
if (
|
|
1696
|
+
if (
|
|
1697
|
+
normalizeMissionAnchorText(proposal.mission) !== distinctMissionAnchors[0] &&
|
|
1698
|
+
!isGenericPlanningMissionAnchor(distinctMissionAnchors[0])
|
|
1699
|
+
) {
|
|
1700
|
+
return undefined;
|
|
1701
|
+
}
|
|
1631
1702
|
if (proposal.scope.length === 0 || proposal.constraints.length === 0 || proposal.acceptance.length === 0) return undefined;
|
|
1632
1703
|
return { ...proposal, source: "session" };
|
|
1633
1704
|
}
|
|
@@ -1648,58 +1719,17 @@ function extractContextProposalFromStructuredSession(
|
|
|
1648
1719
|
async function extractContextProposalFromSession(
|
|
1649
1720
|
ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
|
|
1650
1721
|
projectName: string,
|
|
1651
|
-
explicitGoal?: string,
|
|
1652
1722
|
): Promise<ContextProposal | undefined> {
|
|
1653
1723
|
const recentEntries = collectRecentDiscussionEntries(ctx);
|
|
1654
|
-
return (await analyzeContextProposalWithAgent(ctx, projectName, recentEntries
|
|
1724
|
+
return (await analyzeContextProposalWithAgent(ctx, projectName, recentEntries)) ??
|
|
1655
1725
|
extractContextProposalFromStructuredSession(recentEntries, projectName);
|
|
1656
1726
|
}
|
|
1657
1727
|
|
|
1658
|
-
async function buildGoalAnchoredContextProposal(
|
|
1659
|
-
ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
|
|
1660
|
-
goal: string,
|
|
1661
|
-
projectName: string,
|
|
1662
|
-
): Promise<ContextProposal> {
|
|
1663
|
-
const explicit = parseContextProposal(goal, projectName);
|
|
1664
|
-
const sessionProposal = await extractContextProposalFromSession(ctx, projectName, goal);
|
|
1665
|
-
const missionSource = explicit?.mission ?? goal;
|
|
1666
|
-
const assessment = assessMissionAnchor(missionSource, projectName);
|
|
1667
|
-
const mission = assessment.derived;
|
|
1668
|
-
const mergeSessionBody = sessionProposal?.source === "analyst";
|
|
1669
|
-
const explicitScope = explicit?.scope ?? [];
|
|
1670
|
-
const sessionScope = mergeSessionBody
|
|
1671
|
-
? (sessionProposal?.scope ?? []).filter((item) => isSessionScopeItemMissionRelevant(item, mission))
|
|
1672
|
-
: [];
|
|
1673
|
-
const sessionConstraints = mergeSessionBody ? sessionProposal?.constraints ?? [] : [];
|
|
1674
|
-
const sessionAcceptance = mergeSessionBody ? sessionProposal?.acceptance ?? [] : [];
|
|
1675
|
-
const scope = uniqueProposalItems([...explicitScope, ...sessionScope]);
|
|
1676
|
-
const constraints = uniqueProposalItems([...(explicit?.constraints ?? []), ...sessionConstraints]);
|
|
1677
|
-
const acceptance = uniqueProposalItems([...(explicit?.acceptance ?? []), ...sessionAcceptance]);
|
|
1678
|
-
const analysis = mergeContextProposalAnalysis(
|
|
1679
|
-
[explicit?.analysis, sessionProposal?.analysis],
|
|
1680
|
-
[goal, mission, ...(sessionProposal?.analysis.possibleNoise ?? []), ...scope, ...constraints, ...acceptance],
|
|
1681
|
-
);
|
|
1682
|
-
const goalText = buildContextProposalGoalText({ mission, scope, constraints, acceptance });
|
|
1683
|
-
return {
|
|
1684
|
-
mission,
|
|
1685
|
-
scope,
|
|
1686
|
-
constraints,
|
|
1687
|
-
acceptance,
|
|
1688
|
-
analysis,
|
|
1689
|
-
goalText,
|
|
1690
|
-
basisPreview: sessionProposal?.basisPreview ?? explicit?.basisPreview ?? goal,
|
|
1691
|
-
source: sessionProposal?.source ?? explicit?.source ?? "session",
|
|
1692
|
-
};
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
1728
|
async function deriveCookContextProposal(
|
|
1696
1729
|
ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
|
|
1697
1730
|
projectName: string,
|
|
1698
|
-
explicitGoal?: string,
|
|
1699
1731
|
): Promise<ContextProposal | undefined> {
|
|
1700
|
-
return
|
|
1701
|
-
? await buildGoalAnchoredContextProposal(ctx, explicitGoal, projectName)
|
|
1702
|
-
: await extractContextProposalFromSession(ctx, projectName);
|
|
1732
|
+
return await extractContextProposalFromSession(ctx, projectName);
|
|
1703
1733
|
}
|
|
1704
1734
|
|
|
1705
1735
|
async function confirmContextProposal(
|
|
@@ -1975,44 +2005,37 @@ function buildEvaluationRoleReminderText(snapshot: CompletionStateSnapshot, role
|
|
|
1975
2005
|
async function assessActiveWorkflowProposalRouting(
|
|
1976
2006
|
ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
|
|
1977
2007
|
snapshot: CompletionStateSnapshot,
|
|
1978
|
-
explicitGoal?: string,
|
|
1979
2008
|
): Promise<ActiveWorkflowProposalAssessment> {
|
|
1980
2009
|
const currentMission = currentMissionAnchor(snapshot);
|
|
1981
2010
|
const projectName = path.basename(snapshot.files.root);
|
|
1982
|
-
const proposal =
|
|
1983
|
-
? await deriveCookContextProposal(ctx, projectName, explicitGoal)
|
|
1984
|
-
: await deriveCookContextProposal(ctx, projectName);
|
|
1985
|
-
const mode = explicitGoal ? "explicit" : "bare";
|
|
2011
|
+
const proposal = await deriveCookContextProposal(ctx, projectName);
|
|
1986
2012
|
if (!proposal) {
|
|
1987
2013
|
const assessment: ActiveWorkflowProposalAssessment = {
|
|
1988
2014
|
action: "unclear",
|
|
1989
2015
|
currentMissionAnchor: currentMission,
|
|
1990
2016
|
reason: "missing_proposal",
|
|
1991
2017
|
};
|
|
1992
|
-
maybeWriteActiveWorkflowRoutingSnapshot(assessment
|
|
2018
|
+
maybeWriteActiveWorkflowRoutingSnapshot(assessment);
|
|
1993
2019
|
return assessment;
|
|
1994
2020
|
}
|
|
1995
|
-
|
|
1996
|
-
? missionAnchorsStrictlyEquivalent(currentMission, proposal.mission)
|
|
1997
|
-
: missionAnchorsLikelyEquivalent(currentMission, proposal.mission);
|
|
1998
|
-
if (missionsMatch) {
|
|
2021
|
+
if (missionAnchorsLikelyEquivalent(currentMission, proposal.mission)) {
|
|
1999
2022
|
const assessment: ActiveWorkflowProposalAssessment = {
|
|
2000
2023
|
action: "continue",
|
|
2001
2024
|
currentMissionAnchor: currentMission,
|
|
2002
2025
|
proposal,
|
|
2003
2026
|
reason: "matching_mission",
|
|
2004
2027
|
};
|
|
2005
|
-
maybeWriteActiveWorkflowRoutingSnapshot(assessment
|
|
2028
|
+
maybeWriteActiveWorkflowRoutingSnapshot(assessment);
|
|
2006
2029
|
return assessment;
|
|
2007
2030
|
}
|
|
2008
|
-
if (
|
|
2031
|
+
if (shouldTreatBareActiveWorkflowProposalAsClearRefocus(proposal)) {
|
|
2009
2032
|
const assessment: ActiveWorkflowProposalAssessment = {
|
|
2010
2033
|
action: "refocus",
|
|
2011
2034
|
currentMissionAnchor: currentMission,
|
|
2012
2035
|
proposal,
|
|
2013
|
-
reason:
|
|
2036
|
+
reason: "clear_refocus",
|
|
2014
2037
|
};
|
|
2015
|
-
maybeWriteActiveWorkflowRoutingSnapshot(assessment
|
|
2038
|
+
maybeWriteActiveWorkflowRoutingSnapshot(assessment);
|
|
2016
2039
|
return assessment;
|
|
2017
2040
|
}
|
|
2018
2041
|
const assessment: ActiveWorkflowProposalAssessment = {
|
|
@@ -2021,7 +2044,7 @@ async function assessActiveWorkflowProposalRouting(
|
|
|
2021
2044
|
proposal,
|
|
2022
2045
|
reason: "ambiguous_discussion",
|
|
2023
2046
|
};
|
|
2024
|
-
maybeWriteActiveWorkflowRoutingSnapshot(assessment
|
|
2047
|
+
maybeWriteActiveWorkflowRoutingSnapshot(assessment);
|
|
2025
2048
|
return assessment;
|
|
2026
2049
|
}
|
|
2027
2050
|
|
|
@@ -4050,11 +4073,14 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
4050
4073
|
pi.registerCommand("cook", {
|
|
4051
4074
|
description: "Discussion-driven /cook workflow: start, continue, refocus, or start the next round",
|
|
4052
4075
|
handler: async (args, ctx) => {
|
|
4053
|
-
const
|
|
4054
|
-
|
|
4076
|
+
const inlineArgs = args.trim();
|
|
4077
|
+
if (inlineArgs) {
|
|
4078
|
+
emitCommandText(ctx, COOK_INLINE_ARG_REJECTION_MESSAGE, "info");
|
|
4079
|
+
return;
|
|
4080
|
+
}
|
|
4081
|
+
let goal: string | undefined;
|
|
4055
4082
|
const cwd = getCtxCwd(ctx);
|
|
4056
4083
|
let snapshot = await loadCompletionSnapshot(cwd);
|
|
4057
|
-
const hadSnapshot = Boolean(snapshot);
|
|
4058
4084
|
const workflowDone = isWorkflowDone(snapshot);
|
|
4059
4085
|
let kickoffIntent: "auto" | "continue" | "refocus" = "auto";
|
|
4060
4086
|
let kickoffMissionAnchor = snapshot ? currentMissionAnchor(snapshot) : undefined;
|
|
@@ -4063,40 +4089,21 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
4063
4089
|
if (!snapshot) {
|
|
4064
4090
|
const root = findRepoRoot(cwd) ?? cwd;
|
|
4065
4091
|
const projectName = path.basename(root);
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
return;
|
|
4078
|
-
}
|
|
4079
|
-
goal = decision.goalText;
|
|
4080
|
-
kickoffMissionAnchor = decision.missionAnchor;
|
|
4081
|
-
kickoffAnalysis = decision.analysis;
|
|
4082
|
-
} else {
|
|
4083
|
-
const proposal = await deriveCookContextProposal(ctx, projectName, goal);
|
|
4084
|
-
if (!proposal) {
|
|
4085
|
-
emitCommandText(ctx, "Failed to derive a workflow startup proposal from this goal.", "error");
|
|
4086
|
-
return;
|
|
4087
|
-
}
|
|
4088
|
-
const decision = await confirmContextProposal(ctx, proposal, {
|
|
4089
|
-
title: "Start a completion workflow from this goal?",
|
|
4090
|
-
nonInteractiveBehavior: "accept",
|
|
4091
|
-
});
|
|
4092
|
-
if (!decision) {
|
|
4093
|
-
emitCommandText(ctx, buildCookCancellationMessage("Cancelled workflow startup proposal"), "info");
|
|
4094
|
-
return;
|
|
4095
|
-
}
|
|
4096
|
-
goal = decision.goalText;
|
|
4097
|
-
kickoffMissionAnchor = decision.missionAnchor;
|
|
4098
|
-
kickoffAnalysis = decision.analysis;
|
|
4092
|
+
const proposal = await deriveCookContextProposal(ctx, projectName);
|
|
4093
|
+
if (!proposal) {
|
|
4094
|
+
emitCommandText(ctx, buildCookStructuredDiscussionFailureMessage(), "info");
|
|
4095
|
+
return;
|
|
4096
|
+
}
|
|
4097
|
+
const decision = await confirmContextProposal(ctx, proposal, {
|
|
4098
|
+
title: "Start a completion workflow from the recent discussion?",
|
|
4099
|
+
});
|
|
4100
|
+
if (!decision) {
|
|
4101
|
+
emitCommandText(ctx, buildCookCancellationMessage("Cancelled recent-discussion workflow proposal"), "info");
|
|
4102
|
+
return;
|
|
4099
4103
|
}
|
|
4104
|
+
goal = decision.goalText;
|
|
4105
|
+
kickoffMissionAnchor = decision.missionAnchor;
|
|
4106
|
+
kickoffAnalysis = decision.analysis;
|
|
4100
4107
|
const startupRouting = finalizeContextProposalAnalysis(kickoffAnalysis, [goal ?? kickoffMissionAnchor ?? projectName]);
|
|
4101
4108
|
const created = await scaffoldCompletionFiles(root, kickoffMissionAnchor ?? projectName, {
|
|
4102
4109
|
analysis: startupRouting,
|
|
@@ -4174,73 +4181,6 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
4174
4181
|
}
|
|
4175
4182
|
}
|
|
4176
4183
|
kickoffMissionAnchor = kickoffMissionAnchor ?? currentMissionAnchor(snapshot);
|
|
4177
|
-
if (hadSnapshot && explicitGoal) {
|
|
4178
|
-
if (workflowDone) {
|
|
4179
|
-
const projectName = path.basename(snapshot.files.root);
|
|
4180
|
-
const proposal = await deriveCookContextProposal(ctx, projectName, goal);
|
|
4181
|
-
if (!proposal) {
|
|
4182
|
-
emitCommandText(ctx, "Failed to derive the next workflow round proposal from this goal.", "error");
|
|
4183
|
-
return;
|
|
4184
|
-
}
|
|
4185
|
-
const decision = await confirmContextProposal(ctx, proposal, {
|
|
4186
|
-
title: "Start the next workflow round from this goal?",
|
|
4187
|
-
nonInteractiveBehavior: "accept",
|
|
4188
|
-
});
|
|
4189
|
-
if (!decision) {
|
|
4190
|
-
emitCommandText(ctx, buildCookCancellationMessage("Cancelled next workflow round proposal"), "info");
|
|
4191
|
-
return;
|
|
4192
|
-
}
|
|
4193
|
-
goal = decision.goalText;
|
|
4194
|
-
kickoffIntent = "refocus";
|
|
4195
|
-
kickoffMissionAnchor = decision.missionAnchor;
|
|
4196
|
-
await refocusCompletionMission(snapshot, decision.missionAnchor, decision.goalText, decision.analysis);
|
|
4197
|
-
snapshot = (await loadCompletionSnapshot(snapshot.files.root)) ?? snapshot;
|
|
4198
|
-
emitCommandText(ctx, `Started a new completion workflow round from explicit goal: ${decision.missionAnchor}`, "info");
|
|
4199
|
-
} else {
|
|
4200
|
-
const assessment = await assessActiveWorkflowProposalRouting(ctx, snapshot, goal);
|
|
4201
|
-
if (!assessment.proposal) {
|
|
4202
|
-
emitCommandText(ctx, "Failed to derive the replacement workflow proposal from this goal.", "error");
|
|
4203
|
-
return;
|
|
4204
|
-
}
|
|
4205
|
-
if (assessment.action === "continue") {
|
|
4206
|
-
kickoffIntent = "continue";
|
|
4207
|
-
kickoffMissionAnchor = assessment.currentMissionAnchor;
|
|
4208
|
-
if (normalizeMissionAnchorText(goal) !== normalizeMissionAnchorText(assessment.currentMissionAnchor)) {
|
|
4209
|
-
emitCommandText(ctx, `Continuing existing workflow without changing mission anchor: ${assessment.currentMissionAnchor}`, "info");
|
|
4210
|
-
}
|
|
4211
|
-
} else {
|
|
4212
|
-
const decision = await confirmExistingWorkflowProposal(ctx, snapshot, assessment.proposal, { comparison: "strict" });
|
|
4213
|
-
if (!decision) {
|
|
4214
|
-
emitCommandText(ctx, buildCookCancellationMessage("Cancelled existing workflow confirmation"), "info");
|
|
4215
|
-
return;
|
|
4216
|
-
}
|
|
4217
|
-
kickoffIntent = decision.action;
|
|
4218
|
-
kickoffMissionAnchor = decision.currentMissionAnchor;
|
|
4219
|
-
if (decision.action === "refocus") {
|
|
4220
|
-
const proposalDecision = await confirmContextProposal(ctx, assessment.proposal, {
|
|
4221
|
-
title: "Start the replacement workflow from this goal?",
|
|
4222
|
-
nonInteractiveBehavior: "accept",
|
|
4223
|
-
});
|
|
4224
|
-
if (!proposalDecision) {
|
|
4225
|
-
emitCommandText(ctx, buildCookCancellationMessage("Cancelled replacement workflow proposal"), "info");
|
|
4226
|
-
return;
|
|
4227
|
-
}
|
|
4228
|
-
goal = proposalDecision.goalText;
|
|
4229
|
-
await refocusCompletionMission(
|
|
4230
|
-
snapshot,
|
|
4231
|
-
proposalDecision.missionAnchor,
|
|
4232
|
-
proposalDecision.goalText,
|
|
4233
|
-
proposalDecision.analysis,
|
|
4234
|
-
);
|
|
4235
|
-
snapshot = (await loadCompletionSnapshot(snapshot.files.root)) ?? snapshot;
|
|
4236
|
-
kickoffMissionAnchor = proposalDecision.missionAnchor;
|
|
4237
|
-
emitCommandText(ctx, `Refocused completion mission to: ${proposalDecision.missionAnchor}`, "info");
|
|
4238
|
-
} else if (normalizeMissionAnchorText(goal) !== normalizeMissionAnchorText(decision.currentMissionAnchor)) {
|
|
4239
|
-
emitCommandText(ctx, `Continuing existing workflow without changing mission anchor: ${decision.currentMissionAnchor}`, "info");
|
|
4240
|
-
}
|
|
4241
|
-
}
|
|
4242
|
-
}
|
|
4243
|
-
}
|
|
4244
4184
|
pi.setSessionName(`completion: ${kickoffMissionAnchor.slice(0, 60)}`);
|
|
4245
4185
|
const kickoffPrompt = completionKickoff(
|
|
4246
4186
|
goal,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linimin/pi-letscook",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.40",
|
|
4
4
|
"description": "Pi package for long-running completion workflows with canonical .agent state, role-based subagents, continuity, and verification helpers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|