archondev 2.19.29 → 2.19.31

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.
Files changed (3) hide show
  1. package/README.md +4 -0
  2. package/dist/index.js +177 -9
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -121,6 +121,10 @@ pnpm exec tsx scripts/init-governance-db.ts
121
121
  - Content-only requests (stories, outlines, lessons, visuals) use lightweight planning to avoid blocking.
122
122
  - BYOK shows per‑model usage and cost by today/week/month/year in `archon preferences` → “View usage details.”
123
123
  - You can paste multi‑line requests into interactive prompts; Archon captures them as a single response.
124
+ - Proposal approvals like `approve plan` now bind to the pending proposal context in chat mode.
125
+ - Governance boundary/path checks in execute now steer with actionable guidance and set atoms to `BLOCKED` rather than hard failing.
126
+ - Analysis-first requests now return recommendations first and require explicit `create atom` before task creation.
127
+ - Chat execution now requires explicit execute intent (for example, `execute atom`) instead of generic `continue`.
124
128
 
125
129
  **Tip:** Use `archon plan --edit` to adjust title and acceptance criteria before planning.
126
130
  **Web Checks:** If Archon detects a web project, it prompts to run A11y/SEO/GEO checks and stores your preference in `.archon/config.yaml`.
package/dist/index.js CHANGED
@@ -203,7 +203,7 @@ async function saveAtomEnvironmentState(atomId, cwd, state) {
203
203
  import chalk5 from "chalk";
204
204
  import readline from "readline";
205
205
  import { existsSync as existsSync6, readFileSync as readFileSync3, readdirSync as readdirSync3, appendFileSync } from "fs";
206
- import { join as join6 } from "path";
206
+ import { join as join6, relative } from "path";
207
207
 
208
208
  // src/core/context/manager.ts
209
209
  import { existsSync as existsSync2 } from "fs";
@@ -2616,6 +2616,8 @@ function detectWebProject(cwd) {
2616
2616
  // src/cli/start.ts
2617
2617
  var PAID_TIER_DEFAULT_CHAT_MODEL = "gemini-3.1-pro-preview";
2618
2618
  var pendingProposalRequest = null;
2619
+ var pendingProposalMode = "atom";
2620
+ var pendingAnalysisToAtomRequest = null;
2619
2621
  function uiText(rich, plain) {
2620
2622
  return isTerminalSafeMode() ? plain : rich;
2621
2623
  }
@@ -3676,11 +3678,21 @@ async function runAgentMode(cwd, state) {
3676
3678
  async function handleAgentConversationInput(cwd, input) {
3677
3679
  const normalized = input.trim().toLowerCase();
3678
3680
  if (!normalized) return false;
3681
+ if (pendingAnalysisToAtomRequest && isCreateAtomDirective(normalized)) {
3682
+ const request = pendingAnalysisToAtomRequest;
3683
+ pendingAnalysisToAtomRequest = null;
3684
+ console.log(chalk5.dim("\n> Great. Creating a governed task from the approved analysis plan.\n"));
3685
+ const { plan: plan3 } = await import("./plan-RHDFDSZE.js");
3686
+ await plan3(request, { conversational: true });
3687
+ await showLatestPlannedAtom(cwd);
3688
+ console.log(chalk5.dim('\nReply "execute atom" when you want implementation to start, or tell me what to change.'));
3689
+ return true;
3690
+ }
3679
3691
  if (pendingProposalRequest && isPlanApprovalDirective(normalized)) {
3680
3692
  await applyApprovedProposal(cwd);
3681
3693
  return true;
3682
3694
  }
3683
- if (isContinuationDirective(normalized)) {
3695
+ if (isExecutionDirective(normalized)) {
3684
3696
  await continueWithCurrentTask(cwd);
3685
3697
  return true;
3686
3698
  }
@@ -3689,6 +3701,10 @@ async function handleAgentConversationInput(cwd, input) {
3689
3701
  return true;
3690
3702
  }
3691
3703
  if (wantsProposalBeforeExecution(input)) {
3704
+ if (shouldDoAnalysisBeforeAtom(input)) {
3705
+ await provideAnalysisFirstPlan(cwd, input);
3706
+ return true;
3707
+ }
3692
3708
  await showProposalForApproval(input);
3693
3709
  return true;
3694
3710
  }
@@ -3706,7 +3722,7 @@ async function handleAgentConversationInput(cwd, input) {
3706
3722
  return true;
3707
3723
  }
3708
3724
  await showLatestPlannedAtom(cwd);
3709
- console.log(chalk5.dim('\nReply "continue" when you approve this plan, or tell me what to change.'));
3725
+ console.log(chalk5.dim('\nReply "execute atom" when you want implementation to start, or tell me what to change.'));
3710
3726
  return true;
3711
3727
  }
3712
3728
  function isReadOnlyExploreRequest(input) {
@@ -3733,6 +3749,7 @@ function wantsProposalBeforeExecution(input) {
3733
3749
  }
3734
3750
  async function showProposalForApproval(input) {
3735
3751
  pendingProposalRequest = input.trim();
3752
+ pendingProposalMode = shouldDoAnalysisBeforeAtom(input) ? "analysis" : "atom";
3736
3753
  console.log(chalk5.dim("\n> Understood. I will not create atoms yet.\n"));
3737
3754
  console.log(chalk5.bold("Proposed plan (for your approval):"));
3738
3755
  console.log(chalk5.dim(" 1. Review project files and locate the capsule markdown/source for day 1."));
@@ -3766,18 +3783,32 @@ Showing latest planned atom (${latest.externalId})...
3766
3783
  const { show: show2 } = await import("./show-7A3FACN6.js");
3767
3784
  await show2(latest.externalId);
3768
3785
  }
3769
- function isContinuationDirective(input) {
3786
+ function isPlanApprovalDirective(input) {
3770
3787
  const normalized = input.trim().toLowerCase();
3771
- return normalized === "continue" || normalized === "continue." || normalized === "go on" || normalized === "go ahead" || normalized === "next" || normalized === "proceed" || normalized === "do it";
3788
+ return normalized === "approve" || normalized === "approve plan" || normalized === "approved" || normalized === "yes" || normalized === "yes, proceed" || normalized === "proceed";
3772
3789
  }
3773
- function isPlanApprovalDirective(input) {
3790
+ function isExecutionDirective(input) {
3774
3791
  const normalized = input.trim().toLowerCase();
3775
- return normalized === "approve" || normalized === "approve plan" || normalized === "approved" || normalized === "yes" || normalized === "yes, proceed" || normalized === "proceed" || normalized === "continue" || normalized === "continue.";
3792
+ return normalized === "execute" || normalized === "execute atom" || normalized === "run atom" || normalized === "run it now" || normalized === "implement now" || normalized === "start execution" || normalized === "go ahead and execute";
3793
+ }
3794
+ function isCreateAtomDirective(input) {
3795
+ const normalized = input.trim().toLowerCase();
3796
+ return normalized === "create atom" || normalized === "save this" || normalized === "make atom" || normalized === "save as atom" || normalized === "turn this into a task" || normalized === "create task";
3797
+ }
3798
+ function shouldDoAnalysisBeforeAtom(input) {
3799
+ const normalized = input.toLowerCase();
3800
+ const hasAnalysisIntent = /\b(review|analy[sz]e|inspect|read)\b/.test(normalized) && /\b(files?|folder|project|markdown|lesson|capsule)\b/.test(normalized);
3801
+ const asksForPlan = /\b(let me know|what your plan is|plan is|how to implement|before creating|before you implement)\b/.test(normalized);
3802
+ return hasAnalysisIntent && asksForPlan;
3776
3803
  }
3777
3804
  async function applyApprovedProposal(cwd) {
3778
3805
  const approvedRequest = pendingProposalRequest;
3779
3806
  if (!approvedRequest) return;
3780
3807
  pendingProposalRequest = null;
3808
+ if (pendingProposalMode === "analysis") {
3809
+ await provideAnalysisFirstPlan(cwd, approvedRequest);
3810
+ return;
3811
+ }
3781
3812
  console.log(chalk5.dim("\n> Great. I will create the task from your approved request.\n"));
3782
3813
  const { plan: plan2 } = await import("./plan-RHDFDSZE.js");
3783
3814
  await plan2(approvedRequest, { conversational: true });
@@ -3786,7 +3817,140 @@ async function applyApprovedProposal(cwd) {
3786
3817
  return;
3787
3818
  }
3788
3819
  await showLatestPlannedAtom(cwd);
3789
- console.log(chalk5.dim('\nReply "continue" when you approve this plan, or tell me what to change.'));
3820
+ console.log(chalk5.dim('\nReply "execute atom" when you want implementation to start, or tell me what to change.'));
3821
+ }
3822
+ async function provideAnalysisFirstPlan(cwd, request) {
3823
+ console.log(chalk5.dim("\n> I analyzed your request and generated a recommendation + sample draft first.\n"));
3824
+ const markdownFiles = collectMarkdownFiles(cwd);
3825
+ const dayOneCandidates = markdownFiles.filter((file) => isDayOnePath(file));
3826
+ const capsuleCandidates = markdownFiles.filter((file) => /capsule/i.test(file));
3827
+ const primaryCandidates = dayOneCandidates.length > 0 ? dayOneCandidates : capsuleCandidates;
3828
+ const reviewedFiles = primaryCandidates.slice(0, 8);
3829
+ const stats = computeContentStats(cwd, reviewedFiles);
3830
+ const recommendSplit = stats.totalWords > 900 || stats.totalHeadings >= 6;
3831
+ const capsuleCount = recommendSplit ? 2 : 1;
3832
+ console.log(chalk5.bold("Analysis summary (no atoms created):"));
3833
+ if (reviewedFiles.length === 0) {
3834
+ console.log(chalk5.dim(" - I did not find obvious day-1 capsule markdown files automatically."));
3835
+ console.log(chalk5.dim(" - Share the target file path and I will evaluate it directly."));
3836
+ } else {
3837
+ console.log(chalk5.dim(" Files reviewed:"));
3838
+ for (const file of reviewedFiles) {
3839
+ console.log(chalk5.dim(` - ${file}`));
3840
+ }
3841
+ console.log(chalk5.dim(` Content signals: ~${stats.totalWords} words, ${stats.totalHeadings} section headings.`));
3842
+ }
3843
+ console.log();
3844
+ console.log(chalk5.bold("Recommendation:"));
3845
+ if (reviewedFiles.length === 0) {
3846
+ console.log(chalk5.dim(" I need the exact day-1 capsule file path to make a reliable one-vs-many capsule recommendation."));
3847
+ } else if (recommendSplit) {
3848
+ console.log(chalk5.dim(` Split day 1 into ${capsuleCount} capsules for clarity and lower cognitive load.`));
3849
+ } else {
3850
+ console.log(chalk5.dim(" Keep day 1 as one capsule to preserve continuity and reduce context switching."));
3851
+ }
3852
+ console.log();
3853
+ console.log(chalk5.bold("Implementation plan (proposed):"));
3854
+ console.log(chalk5.dim(" 1. Confirm target source file(s) for day 1 content."));
3855
+ console.log(chalk5.dim(` 2. Define capsule boundaries and titles (${capsuleCount} capsule${capsuleCount > 1 ? "s" : ""}).`));
3856
+ console.log(chalk5.dim(" 3. Draft capsule markdown(s) with consistent template and learning objective per capsule."));
3857
+ console.log(chalk5.dim(" 4. Add index/manifest links and quick validation checks for user flow."));
3858
+ const sampleDraft = buildSampleCapsuleDraft(cwd, reviewedFiles, capsuleCount);
3859
+ if (sampleDraft) {
3860
+ console.log();
3861
+ console.log(chalk5.bold("Sample output for day 1 (draft):"));
3862
+ console.log(sampleDraft);
3863
+ }
3864
+ pendingAnalysisToAtomRequest = request;
3865
+ console.log();
3866
+ console.log(chalk5.dim('Reply "save this" (or "create atom") if you want me to implement this in governed mode.'));
3867
+ }
3868
+ function collectMarkdownFiles(cwd) {
3869
+ const results = [];
3870
+ const queue = ["."];
3871
+ const skipDirs = /* @__PURE__ */ new Set([".git", "node_modules", ".archon", "dist", "build", ".next", ".turbo"]);
3872
+ while (queue.length > 0 && results.length < 200) {
3873
+ const current = queue.shift();
3874
+ if (!current) break;
3875
+ const abs = join6(cwd, current);
3876
+ let entries;
3877
+ try {
3878
+ entries = readdirSync3(abs, { withFileTypes: true });
3879
+ } catch {
3880
+ continue;
3881
+ }
3882
+ for (const entry of entries) {
3883
+ const rel = current === "." ? entry.name : join6(current, entry.name);
3884
+ if (entry.isDirectory()) {
3885
+ if (!skipDirs.has(entry.name)) {
3886
+ queue.push(rel);
3887
+ }
3888
+ continue;
3889
+ }
3890
+ if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
3891
+ results.push(relative(cwd, join6(cwd, rel)).replace(/\\/g, "/"));
3892
+ }
3893
+ }
3894
+ }
3895
+ return results;
3896
+ }
3897
+ function computeContentStats(cwd, files) {
3898
+ let totalWords = 0;
3899
+ let totalHeadings = 0;
3900
+ for (const relPath of files) {
3901
+ try {
3902
+ const content = readFileSync3(join6(cwd, relPath), "utf-8");
3903
+ totalWords += content.split(/\s+/).filter(Boolean).length;
3904
+ totalHeadings += content.split("\n").filter((line) => line.trim().startsWith("#")).length;
3905
+ } catch {
3906
+ }
3907
+ }
3908
+ return { totalWords, totalHeadings };
3909
+ }
3910
+ function isDayOnePath(path2) {
3911
+ const normalized = path2.toLowerCase();
3912
+ return /\b(day[-_ ]?0*1|lesson[-_ ]?0*1|email[-_ ]?day[-_ ]?0*1)\b/.test(normalized) && !/\b(day[-_ ]?(?:1[0-9]|[2-9])|lesson[-_ ]?(?:1[0-9]|[2-9]))\b/.test(normalized);
3913
+ }
3914
+ function buildSampleCapsuleDraft(cwd, files, capsuleCount) {
3915
+ if (files.length === 0) return null;
3916
+ const primary = files[0];
3917
+ if (!primary) return null;
3918
+ let content = "";
3919
+ try {
3920
+ content = readFileSync3(join6(cwd, primary), "utf-8");
3921
+ } catch {
3922
+ return null;
3923
+ }
3924
+ const lines = content.split("\n").map((line) => line.trim());
3925
+ const heading = lines.find((line) => line.startsWith("#"))?.replace(/^#+\s*/, "") || "Day 1 Lesson";
3926
+ const paragraphs = content.split("\n\n").map((p) => p.replace(/\s+/g, " ").trim()).filter((p) => p.length > 40 && !p.startsWith("#") && !p.startsWith("- ") && !p.startsWith("* "));
3927
+ const objective = paragraphs[0] ?? "Introduce the day-1 core idea with a clear, practical framing.";
3928
+ const concept = paragraphs[1] ?? "Explain the key concept in plain language with one relatable example.";
3929
+ const practice = paragraphs[2] ?? "Give one guided prompt so the learner can apply the idea immediately.";
3930
+ if (capsuleCount <= 1) {
3931
+ return [
3932
+ chalk5.dim(" ---"),
3933
+ chalk5.dim(` title: "${heading} - Core Capsule"`),
3934
+ chalk5.dim(' type: "lesson_capsule"'),
3935
+ chalk5.dim(" day: 1"),
3936
+ chalk5.dim(" ---"),
3937
+ chalk5.dim(" ## Objective"),
3938
+ chalk5.dim(` ${objective}`),
3939
+ chalk5.dim(" ## Core Concept"),
3940
+ chalk5.dim(` ${concept}`),
3941
+ chalk5.dim(" ## Practice"),
3942
+ chalk5.dim(` ${practice}`)
3943
+ ].join("\n");
3944
+ }
3945
+ return [
3946
+ chalk5.dim(" Capsule 1: Foundation"),
3947
+ chalk5.dim(` - Objective: ${objective}`),
3948
+ chalk5.dim(` - Core Concept: ${concept}`),
3949
+ chalk5.dim(""),
3950
+ chalk5.dim(" Capsule 2: Application"),
3951
+ chalk5.dim(` - Guided Practice: ${practice}`),
3952
+ chalk5.dim(" - Reflection Prompt: What changed in your understanding after this exercise?")
3953
+ ].join("\n");
3790
3954
  }
3791
3955
  async function continueWithCurrentTask(cwd) {
3792
3956
  const { listLocalAtoms: listLocalAtoms2 } = await import("./plan-RHDFDSZE.js");
@@ -3922,6 +4086,10 @@ function containsActionIntent(input) {
3922
4086
  async function handlePostExploreAction(cwd, request, options = {}) {
3923
4087
  const sourceInput = options.originalInput?.trim() || request;
3924
4088
  if (wantsProposalBeforeExecution(sourceInput)) {
4089
+ if (shouldDoAnalysisBeforeAtom(sourceInput)) {
4090
+ await provideAnalysisFirstPlan(cwd, sourceInput);
4091
+ return;
4092
+ }
3925
4093
  await showProposalForApproval(sourceInput);
3926
4094
  return;
3927
4095
  }
@@ -3944,7 +4112,7 @@ async function handlePostExploreAction(cwd, request, options = {}) {
3944
4112
  return;
3945
4113
  }
3946
4114
  await showLatestPlannedAtom(cwd);
3947
- console.log(chalk5.dim('\nReply "continue" when you approve this plan, or tell me what to change.'));
4115
+ console.log(chalk5.dim('\nReply "execute atom" when you want implementation to start, or tell me what to change.'));
3948
4116
  }
3949
4117
  }
3950
4118
  async function planTask() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archondev",
3
- "version": "2.19.29",
3
+ "version": "2.19.31",
4
4
  "description": "Local-first AI-powered development governance system",
5
5
  "main": "dist/index.js",
6
6
  "bin": {