@linimin/pi-letscook 0.1.37 → 0.1.39

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.
@@ -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" | "explicit_goal" | "missing_proposal" | "ambiguous_discussion";
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 structured Mission/Scope/Constraints/Acceptance proposal. Add that structure in the main chat or, as a temporary compatibility shim, pass /cook <text>.";
545
+ "Bare /cook failed closed because recent discussion did not contain a clear structured Mission/Scope/Constraints/Acceptance proposal. Add that structure 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,88 @@ 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_NO_CODE_DOCS_ONLY_REGEX =
763
+ /(?:\b(?:docs? only|documentation only|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;
764
+ const CONTEXT_PROPOSAL_SUPPORT_ONLY_DOCS_REGEX =
765
+ /(?:^(?:update|edit|document|refresh|write|add)\s+(?:the\s+)?(?:readme|docs?|documentation)\b|^(?:更新|補充|补充|撰寫|撰写|新增)\s*(?:README|文件|文檔|文档))/iu;
766
+ const CONTEXT_PROPOSAL_IMPLEMENTATION_SOURCE_REGEX =
767
+ /(?:\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)\b|(?:修正|修復|修复|更新|新增|移除|恢復|恢复|重構|重构|調整|调整|正規化|规范化|規範化|过滤|過濾|分離|分离|刷新|替換|替换|抑制|對齊|对齐|實作|实现|落地|修補|修补|阻止|允許|允许|轉換|转换|保留|保持))/iu;
768
+
769
+ function contextProposalBodyTexts(proposal: Pick<ContextProposal, "scope" | "constraints" | "acceptance">): string[] {
770
+ return [...proposal.scope, ...proposal.constraints, ...proposal.acceptance];
771
+ }
772
+
773
+ function isGenericPlanningMissionAnchor(text: string): boolean {
774
+ const normalized = normalizeMissionAnchorText(text);
775
+ if (!normalized) return false;
776
+ return CONTEXT_PROPOSAL_GENERIC_PLANNING_MISSION_REGEX.test(normalized);
777
+ }
778
+
779
+ function hasExplicitPlanningOnlyDeliverable(texts: string[]): boolean {
780
+ return texts.some((text) => CONTEXT_PROPOSAL_PLANNING_ONLY_DELIVERABLE_REGEX.test(normalizeProposalLine(text)));
781
+ }
782
+
783
+ function hasClearNoCodeOrDocsOnlySignal(texts: string[]): boolean {
784
+ return texts.some((text) => CONTEXT_PROPOSAL_NO_CODE_DOCS_ONLY_REGEX.test(normalizeProposalLine(text)));
785
+ }
786
+
787
+ function isSupportOnlyDocumentationItem(text: string): boolean {
788
+ return CONTEXT_PROPOSAL_SUPPORT_ONLY_DOCS_REGEX.test(normalizeProposalLine(text));
789
+ }
790
+
791
+ function isImplementationMissionSourceCandidate(text: string): boolean {
792
+ const normalized = normalizeProposalLine(text);
793
+ if (!normalized) return false;
794
+ if (hasExplicitPlanningOnlyDeliverable([normalized])) return false;
795
+ if (hasClearNoCodeOrDocsOnlySignal([normalized])) return false;
796
+ if (isSupportOnlyDocumentationItem(normalized)) return false;
797
+ return CONTEXT_PROPOSAL_IMPLEMENTATION_SOURCE_REGEX.test(normalized);
798
+ }
799
+
800
+ function hasSupportOnlyDocumentationDeliverable(proposal: Pick<ContextProposal, "scope" | "acceptance">): boolean {
801
+ const deliverableTexts = [...proposal.scope, ...proposal.acceptance].map((item) => normalizeProposalLine(item)).filter(Boolean);
802
+ if (deliverableTexts.length === 0) return false;
803
+ return deliverableTexts.every((item) => isSupportOnlyDocumentationItem(item));
804
+ }
805
+
806
+ function pickImplementationMissionSource(proposal: Pick<ContextProposal, "scope" | "constraints" | "acceptance">): string | undefined {
807
+ for (const item of proposal.scope) {
808
+ if (isImplementationMissionSourceCandidate(item)) return item;
809
+ }
810
+ for (const item of proposal.acceptance) {
811
+ if (isImplementationMissionSourceCandidate(item)) return item;
812
+ }
813
+ return undefined;
814
+ }
815
+
816
+ function finalizeContextProposal(proposal: ContextProposal, projectName: string): ContextProposal | undefined {
817
+ if (!isGenericPlanningMissionAnchor(proposal.mission)) return proposal;
818
+ const bodyTexts = contextProposalBodyTexts(proposal);
819
+ if (hasExplicitPlanningOnlyDeliverable(bodyTexts) || hasClearNoCodeOrDocsOnlySignal(bodyTexts) || hasSupportOnlyDocumentationDeliverable(proposal)) {
820
+ return proposal;
821
+ }
822
+ const missionSource = pickImplementationMissionSource(proposal);
823
+ if (!missionSource) return undefined;
824
+ const nextMission = assessMissionAnchor(missionSource, projectName).derived;
825
+ const normalizedNextMission = normalizeMissionAnchorText(nextMission);
826
+ if (!normalizedNextMission || isWeakMissionAnchor(normalizedNextMission)) return undefined;
827
+ if (missionAnchorsStrictlyEquivalent(nextMission, proposal.mission)) return proposal;
828
+ return {
829
+ ...proposal,
830
+ mission: nextMission,
831
+ goalText: buildContextProposalGoalText({
832
+ mission: nextMission,
833
+ scope: proposal.scope,
834
+ constraints: proposal.constraints,
835
+ acceptance: proposal.acceptance,
836
+ }),
837
+ };
838
+ }
839
+
756
840
  const MISSION_SCOPE_FILTER_STOPWORDS = new Set([
757
841
  "a",
758
842
  "an",
@@ -887,19 +971,14 @@ function shouldTreatBareActiveWorkflowProposalAsClearRefocus(proposal: ContextPr
887
971
  );
888
972
  }
889
973
 
890
- function maybeWriteActiveWorkflowRoutingSnapshot(
891
- assessment: ActiveWorkflowProposalAssessment,
892
- options: { mode: "bare" | "explicit"; explicitGoal?: string },
893
- ): void {
974
+ function maybeWriteActiveWorkflowRoutingSnapshot(assessment: ActiveWorkflowProposalAssessment): void {
894
975
  const snapshotPath = completionTestActiveWorkflowRoutingSnapshotPath();
895
976
  if (!snapshotPath) return;
896
977
  maybeWriteTestSnapshot(
897
978
  snapshotPath,
898
979
  `${JSON.stringify(
899
980
  {
900
- mode: options.mode,
901
- explicitGoalProvided: Boolean(options.explicitGoal),
902
- explicitGoal: options.explicitGoal ?? null,
981
+ mode: "bare",
903
982
  action: assessment.action,
904
983
  reason: assessment.reason,
905
984
  currentMissionAnchor: assessment.currentMissionAnchor,
@@ -928,7 +1007,6 @@ const CONTEXT_PROPOSAL_ANALYST_SYSTEM_PROMPT = [
928
1007
  "risks must contain concrete failure modes or regressions that the later workflow should keep in view.",
929
1008
  "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
1009
  "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
1010
  "When discussion is insufficient, prefer empty arrays and a low confidence value over invention.",
933
1011
  ].join(" ");
934
1012
 
@@ -983,11 +1061,7 @@ function extractJsonObjectFromText(text: string): string | undefined {
983
1061
  return unfenced.slice(start, end + 1);
984
1062
  }
985
1063
 
986
- function parseContextProposalAnalystOutput(
987
- raw: string,
988
- projectName: string,
989
- explicitGoal?: string,
990
- ): ContextProposal | undefined {
1064
+ function parseContextProposalAnalystOutput(raw: string, projectName: string): ContextProposal | undefined {
991
1065
  const jsonText = extractJsonObjectFromText(raw);
992
1066
  if (!jsonText) return undefined;
993
1067
  let parsed: unknown;
@@ -997,8 +1071,7 @@ function parseContextProposalAnalystOutput(
997
1071
  return undefined;
998
1072
  }
999
1073
  if (!isRecord(parsed)) return undefined;
1000
- const explicit = explicitGoal ? parseContextProposal(explicitGoal, projectName) : undefined;
1001
- const missionSource = explicit?.mission ?? explicitGoal ?? asString(parsed.mission);
1074
+ const missionSource = asString(parsed.mission);
1002
1075
  if (!missionSource) return undefined;
1003
1076
  const assessment = assessMissionAnchor(missionSource, projectName);
1004
1077
  const normalizedMission = normalizeMissionAnchorText(missionSource);
@@ -1007,31 +1080,28 @@ function parseContextProposalAnalystOutput(
1007
1080
  const scope = uniqueProposalItems(asStringArray(parsed.scope));
1008
1081
  const constraints = uniqueProposalItems(asStringArray(parsed.constraints));
1009
1082
  const acceptance = uniqueProposalItems(asStringArray(parsed.acceptance));
1010
- const analysis = mergeContextProposalAnalysis(
1011
- [
1012
- explicit?.analysis,
1013
- buildContextProposalAnalysis({
1014
- taskType: parsed.task_type ?? parsed.taskType,
1015
- evaluationProfile: parsed.evaluation_profile ?? parsed.evaluationProfile,
1016
- critique: asStringArray(parsed.critique),
1017
- risks: asStringArray(parsed.risks ?? parsed.risk),
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
- );
1083
+ const analysis = buildContextProposalAnalysis({
1084
+ taskType: parsed.task_type ?? parsed.taskType,
1085
+ evaluationProfile: parsed.evaluation_profile ?? parsed.evaluationProfile,
1086
+ critique: asStringArray(parsed.critique),
1087
+ risks: asStringArray(parsed.risks ?? parsed.risk),
1088
+ possibleNoise: asStringArray(parsed.possible_noise ?? parsed.possibleNoise),
1089
+ hintTexts: [raw, mission, ...scope, ...constraints, ...acceptance],
1090
+ });
1024
1091
  const goalText = buildContextProposalGoalText({ mission, scope, constraints, acceptance });
1025
- return {
1026
- mission,
1027
- scope,
1028
- constraints,
1029
- acceptance,
1030
- analysis,
1031
- goalText,
1032
- basisPreview: raw.replace(/\s+/g, " ").trim(),
1033
- source: "analyst",
1034
- };
1092
+ return finalizeContextProposal(
1093
+ {
1094
+ mission,
1095
+ scope,
1096
+ constraints,
1097
+ acceptance,
1098
+ analysis,
1099
+ goalText,
1100
+ basisPreview: raw.replace(/\s+/g, " ").trim(),
1101
+ source: "analyst",
1102
+ },
1103
+ projectName,
1104
+ );
1035
1105
  }
1036
1106
 
1037
1107
  function contextProposalAnalystModelArg(model: unknown): string | undefined {
@@ -1041,17 +1111,9 @@ function contextProposalAnalystModelArg(model: unknown): string | undefined {
1041
1111
  return provider && id ? `${provider}/${id}` : undefined;
1042
1112
  }
1043
1113
 
1044
- function buildContextProposalAnalystPrompt(projectName: string, recentEntries: RecentDiscussionEntry[], explicitGoal?: string): string {
1114
+ function buildContextProposalAnalystPrompt(projectName: string, recentEntries: RecentDiscussionEntry[]): string {
1045
1115
  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");
1116
+ return [`Project: ${projectName}`, "Infer the current mission from the discussion.", "", "Recent discussion:", discussion].join("\n");
1055
1117
  }
1056
1118
 
1057
1119
  function contextProposalAnalystProgressLines(activity: LiveRoleActivity): string[] {
@@ -1080,14 +1142,13 @@ async function runContextProposalAnalystSubprocess(
1080
1142
  ctx: { cwd: string; hasUI: boolean; ui: any; model?: any },
1081
1143
  projectName: string,
1082
1144
  recentEntries: RecentDiscussionEntry[],
1083
- explicitGoal?: string,
1084
1145
  ): Promise<string | undefined> {
1085
1146
  const modelArg = contextProposalAnalystModelArg(ctx.model);
1086
1147
  if (!modelArg) return undefined;
1087
1148
  const cwd = getCtxCwd(ctx);
1088
1149
  const runCwd = findCompletionRoot(cwd) ?? findRepoRoot(cwd) ?? cwd;
1089
1150
  const rootKey = completionRootKey(undefined, cwd);
1090
- const prompt = buildContextProposalAnalystPrompt(projectName, recentEntries, explicitGoal);
1151
+ const prompt = buildContextProposalAnalystPrompt(projectName, recentEntries);
1091
1152
  const systemPromptTemp = await writeTempFile("pi-cook-proposal-analyst-", CONTEXT_PROPOSAL_ANALYST_SYSTEM_PROMPT);
1092
1153
  const analystRole = "cook-proposal-analyst";
1093
1154
  const args: string[] = ["--mode", "json", "-p", "--no-session", "--append-system-prompt", systemPromptTemp.filePath, "--model", modelArg, prompt];
@@ -1189,18 +1250,17 @@ async function analyzeContextProposalWithAgent(
1189
1250
  ctx: { cwd: string; hasUI: boolean; ui: any; model?: any; modelRegistry?: any },
1190
1251
  projectName: string,
1191
1252
  recentEntries: RecentDiscussionEntry[],
1192
- explicitGoal?: string,
1193
1253
  ): Promise<ContextProposal | undefined> {
1194
1254
  if (shouldDisableContextProposalAnalyst()) return undefined;
1195
1255
  const testOutput = completionTestContextProposalAnalystOutput();
1196
1256
  if (testOutput) {
1197
- return parseContextProposalAnalystOutput(testOutput, projectName, explicitGoal);
1257
+ return parseContextProposalAnalystOutput(testOutput, projectName);
1198
1258
  }
1199
1259
  if (recentEntries.length === 0) return undefined;
1200
1260
  try {
1201
- const raw = await runContextProposalAnalystSubprocess(ctx, projectName, recentEntries, explicitGoal);
1261
+ const raw = await runContextProposalAnalystSubprocess(ctx, projectName, recentEntries);
1202
1262
  if (!raw) return undefined;
1203
- return parseContextProposalAnalystOutput(raw, projectName, explicitGoal);
1263
+ return parseContextProposalAnalystOutput(raw, projectName);
1204
1264
  } catch (error) {
1205
1265
  console.warn("[completion] context proposal analyst failed", error);
1206
1266
  return undefined;
@@ -1562,16 +1622,19 @@ function parseContextProposal(text: string, projectName: string): ContextProposa
1562
1622
  hintTexts: [cleaned, mission, ...scope, ...constraints, ...acceptance, ...critique, ...risks],
1563
1623
  });
1564
1624
  const goalText = buildContextProposalGoalText({ mission, scope, constraints, acceptance });
1565
- return {
1566
- mission,
1567
- scope,
1568
- constraints,
1569
- acceptance,
1570
- analysis,
1571
- goalText,
1572
- basisPreview,
1573
- source: "session",
1574
- };
1625
+ return finalizeContextProposal(
1626
+ {
1627
+ mission,
1628
+ scope,
1629
+ constraints,
1630
+ acceptance,
1631
+ analysis,
1632
+ goalText,
1633
+ basisPreview,
1634
+ source: "session",
1635
+ },
1636
+ projectName,
1637
+ );
1575
1638
  }
1576
1639
 
1577
1640
  function hasStructuredContextProposalSignal(text: string): boolean {
@@ -1627,7 +1690,12 @@ function parseStrictStructuredSessionProposal(text: string, projectName: string)
1627
1690
 
1628
1691
  const proposal = parseContextProposal(cleaned, projectName);
1629
1692
  if (!proposal) return undefined;
1630
- if (normalizeMissionAnchorText(proposal.mission) !== distinctMissionAnchors[0]) return undefined;
1693
+ if (
1694
+ normalizeMissionAnchorText(proposal.mission) !== distinctMissionAnchors[0] &&
1695
+ !isGenericPlanningMissionAnchor(distinctMissionAnchors[0])
1696
+ ) {
1697
+ return undefined;
1698
+ }
1631
1699
  if (proposal.scope.length === 0 || proposal.constraints.length === 0 || proposal.acceptance.length === 0) return undefined;
1632
1700
  return { ...proposal, source: "session" };
1633
1701
  }
@@ -1648,58 +1716,17 @@ function extractContextProposalFromStructuredSession(
1648
1716
  async function extractContextProposalFromSession(
1649
1717
  ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
1650
1718
  projectName: string,
1651
- explicitGoal?: string,
1652
1719
  ): Promise<ContextProposal | undefined> {
1653
1720
  const recentEntries = collectRecentDiscussionEntries(ctx);
1654
- return (await analyzeContextProposalWithAgent(ctx, projectName, recentEntries, explicitGoal)) ??
1721
+ return (await analyzeContextProposalWithAgent(ctx, projectName, recentEntries)) ??
1655
1722
  extractContextProposalFromStructuredSession(recentEntries, projectName);
1656
1723
  }
1657
1724
 
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
1725
  async function deriveCookContextProposal(
1696
1726
  ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
1697
1727
  projectName: string,
1698
- explicitGoal?: string,
1699
1728
  ): Promise<ContextProposal | undefined> {
1700
- return explicitGoal
1701
- ? await buildGoalAnchoredContextProposal(ctx, explicitGoal, projectName)
1702
- : await extractContextProposalFromSession(ctx, projectName);
1729
+ return await extractContextProposalFromSession(ctx, projectName);
1703
1730
  }
1704
1731
 
1705
1732
  async function confirmContextProposal(
@@ -1975,44 +2002,37 @@ function buildEvaluationRoleReminderText(snapshot: CompletionStateSnapshot, role
1975
2002
  async function assessActiveWorkflowProposalRouting(
1976
2003
  ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
1977
2004
  snapshot: CompletionStateSnapshot,
1978
- explicitGoal?: string,
1979
2005
  ): Promise<ActiveWorkflowProposalAssessment> {
1980
2006
  const currentMission = currentMissionAnchor(snapshot);
1981
2007
  const projectName = path.basename(snapshot.files.root);
1982
- const proposal = explicitGoal
1983
- ? await deriveCookContextProposal(ctx, projectName, explicitGoal)
1984
- : await deriveCookContextProposal(ctx, projectName);
1985
- const mode = explicitGoal ? "explicit" : "bare";
2008
+ const proposal = await deriveCookContextProposal(ctx, projectName);
1986
2009
  if (!proposal) {
1987
2010
  const assessment: ActiveWorkflowProposalAssessment = {
1988
2011
  action: "unclear",
1989
2012
  currentMissionAnchor: currentMission,
1990
2013
  reason: "missing_proposal",
1991
2014
  };
1992
- maybeWriteActiveWorkflowRoutingSnapshot(assessment, { mode, explicitGoal });
2015
+ maybeWriteActiveWorkflowRoutingSnapshot(assessment);
1993
2016
  return assessment;
1994
2017
  }
1995
- const missionsMatch = explicitGoal
1996
- ? missionAnchorsStrictlyEquivalent(currentMission, proposal.mission)
1997
- : missionAnchorsLikelyEquivalent(currentMission, proposal.mission);
1998
- if (missionsMatch) {
2018
+ if (missionAnchorsLikelyEquivalent(currentMission, proposal.mission)) {
1999
2019
  const assessment: ActiveWorkflowProposalAssessment = {
2000
2020
  action: "continue",
2001
2021
  currentMissionAnchor: currentMission,
2002
2022
  proposal,
2003
2023
  reason: "matching_mission",
2004
2024
  };
2005
- maybeWriteActiveWorkflowRoutingSnapshot(assessment, { mode, explicitGoal });
2025
+ maybeWriteActiveWorkflowRoutingSnapshot(assessment);
2006
2026
  return assessment;
2007
2027
  }
2008
- if (explicitGoal || shouldTreatBareActiveWorkflowProposalAsClearRefocus(proposal)) {
2028
+ if (shouldTreatBareActiveWorkflowProposalAsClearRefocus(proposal)) {
2009
2029
  const assessment: ActiveWorkflowProposalAssessment = {
2010
2030
  action: "refocus",
2011
2031
  currentMissionAnchor: currentMission,
2012
2032
  proposal,
2013
- reason: explicitGoal ? "explicit_goal" : "clear_refocus",
2033
+ reason: "clear_refocus",
2014
2034
  };
2015
- maybeWriteActiveWorkflowRoutingSnapshot(assessment, { mode, explicitGoal });
2035
+ maybeWriteActiveWorkflowRoutingSnapshot(assessment);
2016
2036
  return assessment;
2017
2037
  }
2018
2038
  const assessment: ActiveWorkflowProposalAssessment = {
@@ -2021,7 +2041,7 @@ async function assessActiveWorkflowProposalRouting(
2021
2041
  proposal,
2022
2042
  reason: "ambiguous_discussion",
2023
2043
  };
2024
- maybeWriteActiveWorkflowRoutingSnapshot(assessment, { mode, explicitGoal });
2044
+ maybeWriteActiveWorkflowRoutingSnapshot(assessment);
2025
2045
  return assessment;
2026
2046
  }
2027
2047
 
@@ -4050,11 +4070,14 @@ export default function completionExtension(pi: ExtensionAPI) {
4050
4070
  pi.registerCommand("cook", {
4051
4071
  description: "Discussion-driven /cook workflow: start, continue, refocus, or start the next round",
4052
4072
  handler: async (args, ctx) => {
4053
- const explicitGoal = args.trim();
4054
- let goal = explicitGoal;
4073
+ const inlineArgs = args.trim();
4074
+ if (inlineArgs) {
4075
+ emitCommandText(ctx, COOK_INLINE_ARG_REJECTION_MESSAGE, "info");
4076
+ return;
4077
+ }
4078
+ let goal: string | undefined;
4055
4079
  const cwd = getCtxCwd(ctx);
4056
4080
  let snapshot = await loadCompletionSnapshot(cwd);
4057
- const hadSnapshot = Boolean(snapshot);
4058
4081
  const workflowDone = isWorkflowDone(snapshot);
4059
4082
  let kickoffIntent: "auto" | "continue" | "refocus" = "auto";
4060
4083
  let kickoffMissionAnchor = snapshot ? currentMissionAnchor(snapshot) : undefined;
@@ -4063,40 +4086,21 @@ export default function completionExtension(pi: ExtensionAPI) {
4063
4086
  if (!snapshot) {
4064
4087
  const root = findRepoRoot(cwd) ?? cwd;
4065
4088
  const projectName = path.basename(root);
4066
- if (!goal) {
4067
- const proposal = await deriveCookContextProposal(ctx, projectName);
4068
- if (!proposal) {
4069
- emitCommandText(ctx, buildCookStructuredDiscussionFailureMessage(), "info");
4070
- return;
4071
- }
4072
- const decision = await confirmContextProposal(ctx, proposal, {
4073
- title: "Start a completion workflow from the recent discussion?",
4074
- });
4075
- if (!decision) {
4076
- emitCommandText(ctx, buildCookCancellationMessage("Cancelled recent-discussion workflow proposal"), "info");
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;
4089
+ const proposal = await deriveCookContextProposal(ctx, projectName);
4090
+ if (!proposal) {
4091
+ emitCommandText(ctx, buildCookStructuredDiscussionFailureMessage(), "info");
4092
+ return;
4093
+ }
4094
+ const decision = await confirmContextProposal(ctx, proposal, {
4095
+ title: "Start a completion workflow from the recent discussion?",
4096
+ });
4097
+ if (!decision) {
4098
+ emitCommandText(ctx, buildCookCancellationMessage("Cancelled recent-discussion workflow proposal"), "info");
4099
+ return;
4099
4100
  }
4101
+ goal = decision.goalText;
4102
+ kickoffMissionAnchor = decision.missionAnchor;
4103
+ kickoffAnalysis = decision.analysis;
4100
4104
  const startupRouting = finalizeContextProposalAnalysis(kickoffAnalysis, [goal ?? kickoffMissionAnchor ?? projectName]);
4101
4105
  const created = await scaffoldCompletionFiles(root, kickoffMissionAnchor ?? projectName, {
4102
4106
  analysis: startupRouting,
@@ -4174,73 +4178,6 @@ export default function completionExtension(pi: ExtensionAPI) {
4174
4178
  }
4175
4179
  }
4176
4180
  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
4181
  pi.setSessionName(`completion: ${kickoffMissionAnchor.slice(0, 60)}`);
4245
4182
  const kickoffPrompt = completionKickoff(
4246
4183
  goal,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linimin/pi-letscook",
3
- "version": "0.1.37",
3
+ "version": "0.1.39",
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,
@@ -5,6 +5,45 @@ PKG_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
5
  TMPDIR="$(mktemp -d)"
6
6
  trap 'rm -rf "$TMPDIR"' EXIT
7
7
 
8
+ write_session() {
9
+ local session_path="$1"
10
+ local cwd="$2"
11
+ local text="$3"
12
+ python3 - "$session_path" "$cwd" "$text" <<'PY'
13
+ import json
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ session_path = Path(sys.argv[1])
18
+ cwd = sys.argv[2]
19
+ text = sys.argv[3]
20
+ session_path.parent.mkdir(parents=True, exist_ok=True)
21
+ entries = [
22
+ {
23
+ "type": "session",
24
+ "version": 3,
25
+ "id": "11111111-1111-4111-8111-111111111111",
26
+ "timestamp": "2026-01-01T00:00:00.000Z",
27
+ "cwd": cwd,
28
+ },
29
+ {
30
+ "type": "message",
31
+ "id": "a1b2c3d4",
32
+ "parentId": None,
33
+ "timestamp": "2026-01-01T00:00:01.000Z",
34
+ "message": {
35
+ "role": "user",
36
+ "content": text,
37
+ "timestamp": 1767225601000,
38
+ },
39
+ },
40
+ ]
41
+ with session_path.open('w', encoding='utf-8') as fh:
42
+ for entry in entries:
43
+ fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
44
+ PY
45
+ }
46
+
8
47
  cd "$PKG_ROOT"
9
48
 
10
49
  node <<'NODE'
@@ -46,12 +85,17 @@ NODE
46
85
 
47
86
  ROOT="$TMPDIR/repo"
48
87
  PROMPT="$TMPDIR/resume-prompt.txt"
88
+ BOOTSTRAP_SESSION="$TMPDIR/session-active-slice-bootstrap.jsonl"
89
+ BOOTSTRAP_DISCUSSION=$'Mission: Exercise active-slice contract parity.\nScope:\n- Bootstrap canonical completion files for the active-slice contract fixture.\nConstraints:\n- Use supported bare /cook startup only.\nAcceptance:\n- Materialize canonical files before the fixture rewrites them.'
49
90
  mkdir -p "$ROOT"
50
91
  cd "$ROOT"
51
92
  git init -q
93
+ write_session "$BOOTSTRAP_SESSION" "$ROOT" "$BOOTSTRAP_DISCUSSION"
52
94
 
95
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
96
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
53
97
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
54
- pi -e "$PKG_ROOT" -p "/cook active-slice contract fixture" \
98
+ pi --session "$BOOTSTRAP_SESSION" -e "$PKG_ROOT" -p "/cook" \
55
99
  >"$TMPDIR/pi-active-slice-bootstrap.out" 2>"$TMPDIR/pi-active-slice-bootstrap.err"
56
100
 
57
101
  python3 - <<'PY'