@lazyingart/agintiflow 0.20.57 → 0.20.59

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/README.md CHANGED
@@ -47,6 +47,7 @@ Most agent tools are either a chat box with hidden state or an expensive one-mod
47
47
  | --- | --- |
48
48
  | Cheap intelligence changes the architecture | DeepSeek V4 Flash and Pro make it practical to spend more calls on routing, scouting, review, and recovery instead of forcing one expensive call to do everything. |
49
49
  | Inspectable beats mysterious | Plans, tool calls, file diffs, command output, canvas artifacts, and session events are saved and resumable. |
50
+ | Disciplined by default | `AGINTI.md` starts with a behavior contract: surface ambiguity, keep edits surgical, avoid speculative complexity, verify outcomes, and respect permission blockers. |
50
51
  | Role-based models | Route, main, spare, wrapper, and auxiliary image roles are separate. You can use cheap route models, stronger main models, optional OpenAI/Qwen/Venice routes, and GRS AI/Venice image tools. |
51
52
  | Scouts before big work | Parallel scouts can cheaply map architecture, tests, risks, symbols, and integration points before the main executor edits anything. |
52
53
  | SCS for high-risk work | Student-Committee-Supervisor mode adds a typed gate: committee drafts, student approves/monitors, supervisor executes. Use `/scs` or `--scs auto`. |
@@ -64,6 +65,19 @@ aginti init
64
65
  aginti
65
66
  ```
66
67
 
68
+ `aginti init` creates a disciplined `AGINTI.md` by default: project identity, ambiguity protocol, surgical-change policy, verification contract, permission policy, artifact naming, commands, style, and definition of done. For a smaller or domain-specific starting point:
69
+
70
+ ```bash
71
+ aginti init --list-templates
72
+ aginti init --template minimal
73
+ aginti init --template coding
74
+ aginti init --template research
75
+ aginti init --template writing
76
+ aginti init --template design
77
+ aginti init --template aaps
78
+ aginti init --template supervision
79
+ ```
80
+
67
81
  On first interactive use, AgInTiFlow opens an auth wizard if no main model key is found. Pick DeepSeek, OpenAI, Qwen, or Venice, paste the key, and it saves to the ignored project-local `.aginti/.env` file with restricted permissions. You can rerun setup any time:
68
82
 
69
83
  ```bash
@@ -73,6 +87,16 @@ aginti auth venice
73
87
  aginti login grsai
74
88
  ```
75
89
 
90
+ Provider signup and key pages:
91
+
92
+ | Provider | Register / key page | API base URL used by AgInTiFlow |
93
+ | --- | --- | --- |
94
+ | DeepSeek | [https://platform.deepseek.com/api_keys](https://platform.deepseek.com/api_keys) | `https://api.deepseek.com` |
95
+ | Venice | [https://venice.ai/settings/api](https://venice.ai/settings/api) | `https://api.venice.ai/api/v1` |
96
+ | OpenAI | [https://platform.openai.com/api-keys](https://platform.openai.com/api-keys) | `https://api.openai.com/v1` |
97
+ | Qwen / DashScope | [https://bailian.console.aliyun.com/](https://bailian.console.aliyun.com/) | `https://dashscope-intl.aliyuncs.com/compatible-mode/v1` |
98
+ | GRS AI image tools | [https://grsai.ai/dashboard/api-keys](https://grsai.ai/dashboard/api-keys) | Configure with `/auxiliary grsai` or `aginti login grsai` |
99
+
76
100
  Launch the web UI from the same project:
77
101
 
78
102
  ```bash
@@ -135,6 +159,29 @@ aginti --resume <session-id> \
135
159
 
136
160
  Permission behavior is intentionally consistent: writes inside the current project are allowed through file tools, network/setup runs are normal in approved Docker workspace mode, and outside-project or trusted-host actions stop with a clear blocker plus a suggested rerun command. See [runtime modes and autonomy](docs/runtime-modes-and-autonomy.md) for the full contract.
137
161
 
162
+ ## Permission Recipes
163
+
164
+ Use these when you want explicit control instead of the default interactive policy:
165
+
166
+ | Mode | Command | What it permits |
167
+ | --- | --- | --- |
168
+ | Strict inspection | `aginti --sandbox-mode docker-readonly --package-install-policy block --allow-shell --no-file-tools --no-web-search "inspect this project without edits"` | Enforced read-only project inspection through shell commands such as `ls`, `rg`, `cat`, and test commands that do not write. No file-tool writes, web calls, workspace writes, or installs. |
169
+ | Full write in current folder | `aginti --sandbox-mode docker-workspace --package-install-policy allow --approve-package-installs --allow-shell --allow-file-tools "build and test this project"` | Read/write inside the current project folder, run network/setup commands in Docker, keep host safer. |
170
+ | Full host computer access | `aginti --sandbox-mode host --package-install-policy allow --approve-package-installs --allow-shell --allow-file-tools --allow-destructive "perform the trusted host maintenance task"` | Direct host shell and destructive actions. Use only when you trust the task and want whole-host access. |
171
+
172
+ For resume:
173
+
174
+ ```bash
175
+ aginti --resume <session-id> \
176
+ --sandbox-mode host \
177
+ --package-install-policy allow \
178
+ --approve-package-installs \
179
+ --allow-shell \
180
+ --allow-file-tools \
181
+ --allow-destructive \
182
+ "continue with trusted host access"
183
+ ```
184
+
138
185
  ## Real Screenshots
139
186
 
140
187
  | CLI launch | Web app overview |
@@ -70,6 +70,35 @@ aginti --resume <session-id> \
70
70
  "Continue after approval and verify the output was created in this run."
71
71
  ```
72
72
 
73
+ Three practical permission recipes:
74
+
75
+ ```bash
76
+ # Strict inspection: enforced read-only shell inspection, no file-tool writes, no web, no installs.
77
+ aginti --sandbox-mode docker-readonly \
78
+ --package-install-policy block \
79
+ --allow-shell \
80
+ --no-file-tools \
81
+ --no-web-search \
82
+ "inspect this project without edits"
83
+
84
+ # Full write in the current project folder, with setup commands isolated in Docker.
85
+ aginti --sandbox-mode docker-workspace \
86
+ --package-install-policy allow \
87
+ --approve-package-installs \
88
+ --allow-shell \
89
+ --allow-file-tools \
90
+ "build and test this project"
91
+
92
+ # Full host computer access for trusted maintenance.
93
+ aginti --sandbox-mode host \
94
+ --package-install-policy allow \
95
+ --approve-package-installs \
96
+ --allow-shell \
97
+ --allow-file-tools \
98
+ --allow-destructive \
99
+ "perform the trusted host maintenance task"
100
+ ```
101
+
73
102
  For host-only work, use the stricter trusted form deliberately:
74
103
 
75
104
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.57",
3
+ "version": "0.20.59",
4
4
  "type": "module",
5
5
  "description": "Low-cost, project-aware Web and CLI agents with DeepSeek/Venice/OpenAI routing, visible tool calls, durable sessions, scouts, AAPS, SCS, and guarded local execution.",
6
6
  "license": "Apache-2.0",
@@ -32,10 +32,43 @@ async function runCli(args, envOverrides = {}) {
32
32
  return result.stdout;
33
33
  }
34
34
 
35
+ async function runCliIn(cwd, args, envOverrides = {}) {
36
+ const result = await execFileAsync(process.execPath, [path.join(repoRoot, "bin/aginti-cli.js"), ...args], {
37
+ cwd,
38
+ timeout: 20000,
39
+ maxBuffer: 2 * 1024 * 1024,
40
+ env: {
41
+ ...process.env,
42
+ AGINTIFLOW_RUNTIME_DIR: "",
43
+ AGINTIFLOW_HOME: agintiflowHome,
44
+ ...envOverrides,
45
+ },
46
+ });
47
+ return result.stdout;
48
+ }
49
+
35
50
  try {
36
51
  await runCli(["init"]);
37
52
  const agintiMd = await fs.readFile(path.join(tempRoot, "AGINTI.md"), "utf8");
38
53
  assert(agintiMd.includes("Project instructions for AgInTiFlow agents."), "init did not create AGINTI.md");
54
+ assert(agintiMd.includes("## Agent Operating Contract"), "default init did not include the behavior contract");
55
+ assert(agintiMd.includes("## Verification Contract"), "default init did not include verification contract");
56
+ assert(agintiMd.includes("## Permission And Safety Contract"), "default init did not include permission contract");
57
+ assert(agintiMd.includes("## Definition Of Done"), "default init did not include definition of done");
58
+ const templates = await runCli(["init", "--list-templates"]);
59
+ assert(templates.includes("disciplined") && templates.includes("supervision"), "init did not list instruction templates");
60
+ const minimalRoot = path.join(tempRoot, "minimal-template");
61
+ await fs.mkdir(minimalRoot);
62
+ const minimalOutput = await runCliIn(minimalRoot, ["init", "--template", "minimal"]);
63
+ const minimalMd = await fs.readFile(path.join(minimalRoot, "AGINTI.md"), "utf8");
64
+ assert(minimalOutput.includes("template=minimal"), "minimal init did not report template");
65
+ assert(minimalMd.includes("## Agent Contract"), "minimal template did not create compact contract");
66
+ assert(!minimalMd.includes("## Architecture Notes"), "minimal template should stay compact");
67
+ const codingRoot = path.join(tempRoot, "coding-template");
68
+ await fs.mkdir(codingRoot);
69
+ await runCliIn(codingRoot, ["init", "coding"]);
70
+ const codingMd = await fs.readFile(path.join(codingRoot, "AGINTI.md"), "utf8");
71
+ assert(codingMd.includes("## Coding Profile Notes"), "coding template did not add coding appendix");
39
72
  const capabilities = JSON.parse(await runCli(["capabilities", "--json"]));
40
73
  assert(capabilities.project.root === tempRoot, "capabilities did not use cwd as project root");
41
74
  assert(capabilities.project.commandCwd === tempRoot, "capabilities did not default commandCwd to project root");
@@ -168,6 +201,7 @@ try {
168
201
  "git-policy",
169
202
  "skills-capability",
170
203
  "env-sandbox-defaults",
204
+ "aginti-md-contract-templates",
171
205
  ],
172
206
  },
173
207
  null,
@@ -28,6 +28,7 @@ import { captureTmuxPane, listTmuxSessions, sendTmuxKeys, startTmuxSession } fro
28
28
  import { languageInstruction } from "./i18n.js";
29
29
  import { flushHousekeeping } from "./housekeeping.js";
30
30
  import { buildFailedCommandAdvice, buildPermissionAdvice } from "./permission-advice.js";
31
+ import { formatBehaviorContractForPrompt } from "./behavior-contract.js";
31
32
  import {
32
33
  buildSupervisorInstruction,
33
34
  createScsPlan,
@@ -353,6 +354,7 @@ async function createInitialState(config, sessionId) {
353
354
  "Avoid destructive actions, purchases, account changes, and sensitive workflows.",
354
355
  languageInstruction(config.language || "en"),
355
356
  projectInstructionContext,
357
+ formatBehaviorContractForPrompt(),
356
358
  "Treat AGINTI.md as durable project memory and operating instructions for this project. The user can edit it manually or ask you in chat to update it; use workspace file tools for that and never store secrets there.",
357
359
  config.allowShellTool
358
360
  ? config.useDockerSandbox
@@ -41,13 +41,14 @@ export const MAIN_AUTH_PROVIDERS = [
41
41
  label: "Qwen",
42
42
  keyName: "QWEN_API_KEY",
43
43
  description: "Qwen OpenAI-compatible route",
44
+ keyUrl: "https://bailian.console.aliyun.com/",
44
45
  },
45
46
  {
46
47
  id: "venice",
47
48
  label: "Venice",
48
49
  keyName: "VENICE_API_KEY",
49
50
  description: "Venice OpenAI-compatible text and image routes",
50
- keyUrl: "https://venice.ai",
51
+ keyUrl: "https://venice.ai/settings/api",
51
52
  },
52
53
  ];
53
54
 
@@ -56,6 +57,7 @@ const AUXILIARY_AUTH_PROVIDER = {
56
57
  label: "GRS AI / Nano Banana",
57
58
  keyName: "GRSAI",
58
59
  description: "optional image generation",
60
+ keyUrl: "https://grsai.ai/dashboard/api-keys",
59
61
  };
60
62
 
61
63
  const AUTH_ALIASES = {
@@ -0,0 +1,290 @@
1
+ export const INSTRUCTION_TEMPLATE_IDS = [
2
+ "minimal",
3
+ "disciplined",
4
+ "coding",
5
+ "research",
6
+ "writing",
7
+ "design",
8
+ "aaps",
9
+ "supervision",
10
+ ];
11
+
12
+ const TEMPLATE_DESCRIPTIONS = {
13
+ minimal: "Short project memory for tiny or experimental folders.",
14
+ disciplined: "Default robust agent contract for normal project work.",
15
+ coding: "Coding-focused contract with commands, tests, style, and architecture notes.",
16
+ research: "Research-focused contract with sources, reproducibility, and citation notes.",
17
+ writing: "Writing-focused contract with audience, style, outline, and publication notes.",
18
+ design: "Design-focused contract with visual system, assets, QA, and artifact notes.",
19
+ aaps: "AAPS workflow contract with phase criteria, tools, checks, and artifacts.",
20
+ supervision: "Supervisor/student contract for monitored agent work and evidence gates.",
21
+ };
22
+
23
+ const TEMPLATE_ALIASES = {
24
+ default: "disciplined",
25
+ standard: "disciplined",
26
+ full: "disciplined",
27
+ careful: "disciplined",
28
+ karpathy: "disciplined",
29
+ code: "coding",
30
+ dev: "coding",
31
+ software: "coding",
32
+ paper: "research",
33
+ docs: "writing",
34
+ doc: "writing",
35
+ article: "writing",
36
+ visual: "design",
37
+ ui: "design",
38
+ workflow: "aaps",
39
+ student: "supervision",
40
+ supervise: "supervision",
41
+ };
42
+
43
+ export function normalizeInstructionTemplate(value = "disciplined", fallback = "disciplined") {
44
+ const raw = String(value || "").trim().toLowerCase();
45
+ const normalized = TEMPLATE_ALIASES[raw] || raw;
46
+ if (INSTRUCTION_TEMPLATE_IDS.includes(normalized)) return normalized;
47
+ return INSTRUCTION_TEMPLATE_IDS.includes(fallback) ? fallback : "disciplined";
48
+ }
49
+
50
+ export function listInstructionTemplates() {
51
+ return INSTRUCTION_TEMPLATE_IDS.map((id) => ({
52
+ id,
53
+ description: TEMPLATE_DESCRIPTIONS[id] || "",
54
+ }));
55
+ }
56
+
57
+ export function formatInstructionTemplateList() {
58
+ return listInstructionTemplates()
59
+ .map((item) => `${item.id.padEnd(12)} ${item.description}`)
60
+ .join("\n");
61
+ }
62
+
63
+ export function formatBehaviorContractForPrompt({ mode = "runtime" } = {}) {
64
+ const prefix = mode === "plan" ? "Planning discipline contract:" : "AgInTiFlow discipline contract:";
65
+ return [
66
+ prefix,
67
+ "Surface ambiguity instead of silently guessing when interpretations change scope, safety, or implementation.",
68
+ "Prefer the smallest coherent change that satisfies the request; do not add speculative features or abstractions.",
69
+ "Make surgical edits: no drive-by refactors, unrelated formatting churn, or deletion of code you did not need to touch.",
70
+ "Define or infer concrete success criteria for non-trivial work, then run focused checks or state why checks are unavailable.",
71
+ "Respect the permission contract: if a tool is blocked, stop and present the safe rerun/approval path instead of retrying variants.",
72
+ "Keep artifacts durable and discoverable with descriptive non-conflicting names; never overwrite unless the user clearly asked.",
73
+ ].join(" ");
74
+ }
75
+
76
+ export function scsContractCriteria() {
77
+ return [
78
+ "Assumptions and ambiguities are explicit enough for the phase.",
79
+ "The phase is the smallest coherent step toward the user goal.",
80
+ "The plan avoids speculative features, broad rewrites, and unrelated cleanup.",
81
+ "The phase has concrete success criteria and an evidence/check path.",
82
+ "Permission, secret, destructive-action, and artifact-overwrite risks are called out.",
83
+ ];
84
+ }
85
+
86
+ function lines(...items) {
87
+ return items.join("\n");
88
+ }
89
+
90
+ function baseSections() {
91
+ return lines(
92
+ "# AGINTI.md",
93
+ "",
94
+ "Project instructions for AgInTiFlow agents.",
95
+ "",
96
+ "This file is durable project memory. Edit it directly or ask AgInTiFlow to update it during chat. Keep secrets in `.aginti/.env`, not here.",
97
+ "",
98
+ "## Project Identity",
99
+ "",
100
+ "- Project name:",
101
+ "- What this project does:",
102
+ "- Primary users:",
103
+ "- Main workflows to preserve:",
104
+ "- Explicit non-goals:",
105
+ "",
106
+ "## Current Priorities",
107
+ "",
108
+ "-",
109
+ "",
110
+ "## Agent Operating Contract",
111
+ "",
112
+ "- Inspect before editing: read this file, relevant README/docs, manifests, entry points, tests, and exact files related to the request.",
113
+ "- State assumptions when the request is ambiguous. If multiple interpretations would lead to different implementations, ask or present options before editing.",
114
+ "- Prefer the smallest coherent change that solves the user's actual request.",
115
+ "- Do not add speculative features, abstractions, configurability, rewrites, or broad refactors unless requested.",
116
+ "- Do not change adjacent formatting, comments, naming, or style just because it looks improvable.",
117
+ "- Every changed line should trace to the task or to cleanup caused by the task.",
118
+ "- Match existing project style even if another style is personally preferable.",
119
+ "- If you notice unrelated issues, report them separately rather than editing them.",
120
+ "",
121
+ "## Verification Contract",
122
+ "",
123
+ "For non-trivial work, define success criteria before implementation:",
124
+ "",
125
+ "- Target behavior:",
126
+ "- Files or surfaces likely affected:",
127
+ "- Verification command(s):",
128
+ "- Manual checks, if needed:",
129
+ "",
130
+ "Preferred loop:",
131
+ "",
132
+ "1. Reproduce or inspect the issue.",
133
+ "2. Make the smallest coherent change.",
134
+ "3. Run focused checks.",
135
+ "4. Repair failures caused by the change.",
136
+ "5. Summarize changed files, checks run, and residual risks.",
137
+ "",
138
+ "Do not claim success without a concrete check, unless no check exists and that limitation is stated.",
139
+ "",
140
+ "## Permission And Safety Contract",
141
+ "",
142
+ "- Current project folder writes are allowed when file tools are enabled.",
143
+ "- Do not write outside this project unless the user explicitly asks and the runtime permits it.",
144
+ "- Never print or store secrets in logs, docs, commits, screenshots, or artifacts.",
145
+ "- Do not edit `.git`, `.env`, dependency caches, generated vendor folders, or large binary files unless explicitly requested.",
146
+ "- Destructive actions, host maintenance, sudo, publishing, deployment, and broad cleanup require explicit user intent and the appropriate runtime mode.",
147
+ "- If blocked by policy, stop and suggest the safest rerun command instead of trying command variants.",
148
+ "",
149
+ "## File And Artifact Policy",
150
+ "",
151
+ "- Use descriptive, non-conflicting filenames for generated docs, stories, images, reports, screenshots, and artifacts.",
152
+ "- Avoid generic names such as `output.txt`, `story.txt`, or `result.png` unless the user asked for that exact path.",
153
+ "- Do not overwrite existing files unless the user asked to update, replace, patch, or overwrite them.",
154
+ "- Keep durable outputs in project folders where the user can find them.",
155
+ "",
156
+ "## Commands",
157
+ "",
158
+ "- Install:",
159
+ "- Build:",
160
+ "- Test:",
161
+ "- Lint:",
162
+ "- Typecheck:",
163
+ "- Format:",
164
+ "- Preview/run:",
165
+ "- Deploy/publish:",
166
+ "",
167
+ "## Architecture Notes",
168
+ "",
169
+ "- Main entry points:",
170
+ "- Important directories:",
171
+ "- Generated directories:",
172
+ "- Files agents should avoid:",
173
+ "- External services:",
174
+ "",
175
+ "## Style And Conventions",
176
+ "",
177
+ "- Language/runtime:",
178
+ "- Package manager:",
179
+ "- Formatting style:",
180
+ "- Test framework:",
181
+ "- Error handling style:",
182
+ "- Naming conventions:",
183
+ "",
184
+ "## Definition Of Done",
185
+ "",
186
+ "A task is done when:",
187
+ "",
188
+ "- The requested behavior is implemented or the blocker is clearly reported.",
189
+ "- Relevant checks were run, or missing checks are stated.",
190
+ "- The diff is scoped to the request.",
191
+ "- Generated artifacts are named clearly.",
192
+ "- Git status and residual risks are summarized when relevant."
193
+ );
194
+ }
195
+
196
+ function minimalSections() {
197
+ return lines(
198
+ "# AGINTI.md",
199
+ "",
200
+ "Project instructions for AgInTiFlow agents.",
201
+ "",
202
+ "Keep secrets in `.aginti/.env`, not here.",
203
+ "",
204
+ "## Project",
205
+ "",
206
+ "- Purpose:",
207
+ "- Important commands:",
208
+ "- Files or directories to avoid:",
209
+ "",
210
+ "## Agent Contract",
211
+ "",
212
+ "- Inspect relevant files before editing.",
213
+ "- Prefer small, surgical changes.",
214
+ "- Avoid speculative features and broad refactors.",
215
+ "- Run focused checks when available.",
216
+ "- Use descriptive non-conflicting filenames for generated outputs."
217
+ );
218
+ }
219
+
220
+ function templateAppendix(template) {
221
+ if (template === "coding") {
222
+ return lines(
223
+ "",
224
+ "## Coding Profile Notes",
225
+ "",
226
+ "- Prefer tests or focused repro commands before and after fixes.",
227
+ "- Keep public APIs stable unless the task explicitly changes them.",
228
+ "- Check callers before changing shared functions.",
229
+ "- Record package-manager and test commands as they become known."
230
+ );
231
+ }
232
+ if (template === "research") {
233
+ return lines(
234
+ "",
235
+ "## Research Profile Notes",
236
+ "",
237
+ "- Track sources, dates accessed, datasets, assumptions, and limitations.",
238
+ "- Prefer primary sources and reproducible scripts/notebooks when possible.",
239
+ "- Separate evidence, interpretation, and speculation."
240
+ );
241
+ }
242
+ if (template === "writing") {
243
+ return lines(
244
+ "",
245
+ "## Writing Profile Notes",
246
+ "",
247
+ "- Track audience, tone, outline, publication target, and reference style.",
248
+ "- Preserve the user's voice; do not over-polish into generic AI prose.",
249
+ "- Keep drafts, revisions, and final exports clearly named."
250
+ );
251
+ }
252
+ if (template === "design") {
253
+ return lines(
254
+ "",
255
+ "## Design Profile Notes",
256
+ "",
257
+ "- Define visual direction, audience, assets, constraints, and acceptance screenshots.",
258
+ "- Prefer intentional typography, spacing, color, and motion over generic layouts.",
259
+ "- Save source assets and exported previews with durable names."
260
+ );
261
+ }
262
+ if (template === "aaps") {
263
+ return lines(
264
+ "",
265
+ "## AAPS Profile Notes",
266
+ "",
267
+ "- Treat `.aaps` files as top-down workflow specifications.",
268
+ "- Each phase should list goal, allowed tools, write scope, checks, artifacts, and stop conditions.",
269
+ "- Validate or compile workflows before reporting success."
270
+ );
271
+ }
272
+ if (template === "supervision") {
273
+ return lines(
274
+ "",
275
+ "## Supervision Profile Notes",
276
+ "",
277
+ "- The supervised agent does the actual project work; the supervisor monitors evidence and capability gaps.",
278
+ "- Verify artifacts directly instead of trusting self-reports.",
279
+ "- If the student fails due to missing skill/tool/policy, improve AgInTiFlow and resume the same session."
280
+ );
281
+ }
282
+ return "";
283
+ }
284
+
285
+ export function buildAgintiInstructions(template = "disciplined") {
286
+ const normalized = normalizeInstructionTemplate(template);
287
+ const body = normalized === "minimal" ? minimalSections() : `${baseSections()}${templateAppendix(normalized)}`;
288
+ return `${body}\n`;
289
+ }
290
+
package/src/cli.js CHANGED
@@ -35,6 +35,7 @@ import { maybeAutoUpdate } from "./auto-update.js";
35
35
  import { readHousekeepingSummary } from "./housekeeping.js";
36
36
  import { handleSkillMeshCommand } from "./skillmesh.js";
37
37
  import { handleAapsCliCommand } from "./aaps-adapter.js";
38
+ import { formatInstructionTemplateList, normalizeInstructionTemplate } from "./behavior-contract.js";
38
39
  import fs from "node:fs/promises";
39
40
  import path from "node:path";
40
41
  import { fileURLToPath } from "node:url";
@@ -404,7 +405,7 @@ export function parseArgs(argv) {
404
405
 
405
406
  function printUsage() {
406
407
  console.log(
407
- 'Usage: aginti [chat] OR aginti web [--port 3210] OR aginti update OR aginti models OR aginti aaps [status|init|files|validate|compile|check|run] OR aginti skills [query] OR aginti skillmesh [status|off|record|share|sync|serve|service] OR aginti housekeeping [--json] OR aginti auth [deepseek|openai|qwen|venice|grsai] OR aginti resume [--all-sessions] [latest|<session-id>] ["prompt"] OR aginti --remove-empty-sessions OR aginti --remove-sessions OR aginti queue <session-id> "message" OR aginti [--no-auto-update] [--language en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru] [--image] [--latex] [--scs|--scs auto|--no-scs] [--routing smart|fast|complex|manual] [--provider deepseek|openai|qwen|venice|mock] [--model MODEL] [--route-model MODEL] [--main-model MODEL] [--spare-model MODEL --spare-reasoning medium] [--aux-provider grsai|venice --aux-model MODEL] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--approve-package-installs] [--allow-shell|--no-shell] [--allow-file-tools|--no-file-tools] [--web-search|--no-web-search] [--parallel-scouts|--no-parallel-scouts --scout-count 1..10] [--allow-auxiliary-tools|--no-auxiliary-tools] [--allow-wrappers --wrapper codex --wrapper-model gpt-5.5] [--list-models|--list-routes] "your task"'
408
+ 'Usage: aginti [chat] OR aginti init [--template minimal|disciplined|coding|research|writing|design|aaps|supervision] OR aginti web [--port 3210] OR aginti update OR aginti models OR aginti aaps [status|init|files|validate|compile|check|run] OR aginti skills [query] OR aginti skillmesh [status|off|record|share|sync|serve|service] OR aginti housekeeping [--json] OR aginti auth [deepseek|openai|qwen|venice|grsai] OR aginti resume [--all-sessions] [latest|<session-id>] ["prompt"] OR aginti --remove-empty-sessions OR aginti --remove-sessions OR aginti queue <session-id> "message" OR aginti [--no-auto-update] [--language en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru] [--image] [--latex] [--scs|--scs auto|--no-scs] [--routing smart|fast|complex|manual] [--provider deepseek|openai|qwen|venice|mock] [--model MODEL] [--route-model MODEL] [--main-model MODEL] [--spare-model MODEL --spare-reasoning medium] [--aux-provider grsai|venice --aux-model MODEL] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--approve-package-installs] [--allow-shell|--no-shell] [--allow-file-tools|--no-file-tools] [--web-search|--no-web-search] [--parallel-scouts|--no-parallel-scouts --scout-count 1..10] [--allow-auxiliary-tools|--no-auxiliary-tools] [--allow-wrappers --wrapper codex --wrapper-model gpt-5.5] [--list-models|--list-routes] "your task"'
408
409
  );
409
410
  console.log(`Languages: ${["en", "ja", "zh-Hans", "zh-Hant", "ko", "fr", "es", "ar", "vi", "de", "ru"].map((code) => `${code}=${languageLabel(code)}`).join(", ")}`);
410
411
  }
@@ -585,11 +586,34 @@ async function printHousekeeping(argv = []) {
585
586
  function printInitResult(result) {
586
587
  console.log(`AgInTiFlow project initialized: ${result.projectRoot}`);
587
588
  console.log(`instructions=${result.instructionsPath}`);
589
+ if (result.template) console.log(`template=${result.template}`);
588
590
  console.log(`control=${result.controlDir}`);
589
591
  console.log(`projectSessions=${result.sessionsDir}`);
590
592
  console.log(`created=${result.created.length} updated=${result.updated.length} skipped=${result.skipped.length}`);
591
593
  }
592
594
 
595
+ function parseInitOptions(argv = []) {
596
+ let template = "disciplined";
597
+ let list = false;
598
+ for (let index = 0; index < argv.length; index += 1) {
599
+ const arg = argv[index];
600
+ if (arg === "--template" || arg === "-t") {
601
+ template = readOption(argv, index) || template;
602
+ index += 1;
603
+ continue;
604
+ }
605
+ if (arg === "--list-templates" || arg === "templates" || arg === "list") {
606
+ list = true;
607
+ continue;
608
+ }
609
+ if (!arg.startsWith("-")) template = arg;
610
+ }
611
+ return {
612
+ template: normalizeInstructionTemplate(template),
613
+ list,
614
+ };
615
+ }
616
+
593
617
  function printDoctorReport(report) {
594
618
  console.log(`AgInTiFlow ${report.package.version} (npm latest: ${report.package.npmLatest})`);
595
619
  console.log(`node=${report.node.version} ok=${report.node.ok}`);
@@ -1101,66 +1125,73 @@ export async function main(argv = process.argv.slice(2)) {
1101
1125
  return;
1102
1126
  }
1103
1127
 
1104
- if (argv.includes("--remove-empty-sessions") || argv[0] === "remove-empty-sessions") {
1128
+ if (commandArgv.includes("--remove-empty-sessions") || commandArgv[0] === "remove-empty-sessions") {
1105
1129
  await handleRemoveSessionsCommand({ emptyOnly: true });
1106
1130
  return;
1107
1131
  }
1108
1132
 
1109
- if (argv.includes("--remove-sessions") || argv[0] === "remove-sessions") {
1133
+ if (commandArgv.includes("--remove-sessions") || commandArgv[0] === "remove-sessions") {
1110
1134
  await handleRemoveSessionsCommand({ emptyOnly: false });
1111
1135
  return;
1112
1136
  }
1113
1137
 
1114
- if (argv[0] === "init") {
1115
- printInitResult(await initProject(process.cwd()));
1138
+ if (commandArgv[0] === "init") {
1139
+ const initOptions = parseInitOptions(commandArgv.slice(1));
1140
+ if (initOptions.list) {
1141
+ console.log(formatInstructionTemplateList());
1142
+ return;
1143
+ }
1144
+ printInitResult(await initProject(commandCwd, { template: initOptions.template }));
1116
1145
  return;
1117
1146
  }
1118
1147
 
1119
- if (argv[0] === "doctor") {
1120
- const parsed = parseArgs(argv.slice(1).filter((arg) => arg !== "--json" && arg !== "--capabilities"));
1148
+ if (commandArgv[0] === "doctor") {
1149
+ const parsed = parseArgs(commandArgv.slice(1).filter((arg) => arg !== "--json" && arg !== "--capabilities"));
1121
1150
  const config = loadConfig(
1122
1151
  {
1123
1152
  ...parsed,
1124
1153
  goal: "doctor",
1154
+ commandCwd,
1125
1155
  allowShellTool: parsed.allowShellTool ?? true,
1126
1156
  allowFileTools: parsed.allowFileTools ?? true,
1127
1157
  },
1128
- { packageDir, baseDir: process.cwd() }
1158
+ { packageDir, baseDir: commandCwd }
1129
1159
  );
1130
- const report = argv.includes("--capabilities")
1131
- ? await buildCapabilityReport(process.cwd(), packageJson.version, config)
1132
- : await doctorReport(process.cwd(), packageJson.version, config);
1133
- if (argv.includes("--json")) console.log(JSON.stringify(report, null, 2));
1134
- else if (argv.includes("--capabilities")) printCapabilityReport(report);
1160
+ const report = commandArgv.includes("--capabilities")
1161
+ ? await buildCapabilityReport(commandCwd, packageJson.version, config)
1162
+ : await doctorReport(commandCwd, packageJson.version, config);
1163
+ if (commandArgv.includes("--json")) console.log(JSON.stringify(report, null, 2));
1164
+ else if (commandArgv.includes("--capabilities")) printCapabilityReport(report);
1135
1165
  else printDoctorReport(report);
1136
1166
  return;
1137
1167
  }
1138
1168
 
1139
- if (argv[0] === "capabilities") {
1140
- const parsed = parseArgs(argv.slice(1).filter((arg) => arg !== "--json"));
1169
+ if (commandArgv[0] === "capabilities") {
1170
+ const parsed = parseArgs(commandArgv.slice(1).filter((arg) => arg !== "--json"));
1141
1171
  const config = loadConfig(
1142
1172
  {
1143
1173
  ...parsed,
1144
1174
  goal: "capabilities",
1175
+ commandCwd,
1145
1176
  allowShellTool: parsed.allowShellTool ?? true,
1146
1177
  allowFileTools: parsed.allowFileTools ?? true,
1147
1178
  },
1148
- { packageDir, baseDir: process.cwd() }
1179
+ { packageDir, baseDir: commandCwd }
1149
1180
  );
1150
- const report = await buildCapabilityReport(process.cwd(), packageJson.version, config);
1151
- if (argv.includes("--json")) console.log(JSON.stringify(report, null, 2));
1181
+ const report = await buildCapabilityReport(commandCwd, packageJson.version, config);
1182
+ if (commandArgv.includes("--json")) console.log(JSON.stringify(report, null, 2));
1152
1183
  else printCapabilityReport(report);
1153
1184
  return;
1154
1185
  }
1155
1186
 
1156
- if (argv[0] === "housekeeping" || argv[0] === "housekeeper") {
1157
- await printHousekeeping(argv.slice(1));
1187
+ if (commandArgv[0] === "housekeeping" || commandArgv[0] === "housekeeper") {
1188
+ await printHousekeeping(commandArgv.slice(1));
1158
1189
  return;
1159
1190
  }
1160
1191
 
1161
- if (argv[0] === "skillmesh" || argv[0] === "skillsync" || argv[0] === "skill-sync" || argv[0] === "skill-share") {
1192
+ if (commandArgv[0] === "skillmesh" || commandArgv[0] === "skillsync" || commandArgv[0] === "skill-sync" || commandArgv[0] === "skill-share") {
1162
1193
  try {
1163
- await handleSkillMeshCommand(argv.slice(1));
1194
+ await handleSkillMeshCommand(commandArgv.slice(1));
1164
1195
  } catch (error) {
1165
1196
  console.error(error instanceof Error ? error.message : String(error));
1166
1197
  process.exit(1);
@@ -1168,63 +1199,63 @@ export async function main(argv = process.argv.slice(2)) {
1168
1199
  return;
1169
1200
  }
1170
1201
 
1171
- if (argv[0] === "keys/status") {
1202
+ if (commandArgv[0] === "keys/status") {
1172
1203
  await handleKeyCommand(["status"]);
1173
1204
  return;
1174
1205
  }
1175
1206
 
1176
- if (argv[0] === "keys") {
1177
- await handleKeyCommand(argv.slice(1));
1207
+ if (commandArgv[0] === "keys") {
1208
+ await handleKeyCommand(commandArgv.slice(1));
1178
1209
  return;
1179
1210
  }
1180
1211
 
1181
- if (argv[0] === "auth" || argv[0] === "login") {
1182
- const provider = normalizeAuthProvider(argv[1] || "", "");
1183
- if (argv[0] === "auth" || (!provider && process.stdin.isTTY)) {
1184
- const result = await runAuthWizard(process.cwd(), { provider });
1212
+ if (commandArgv[0] === "auth" || commandArgv[0] === "login") {
1213
+ const provider = normalizeAuthProvider(commandArgv[1] || "", "");
1214
+ if (commandArgv[0] === "auth" || (!provider && process.stdin.isTTY)) {
1215
+ const result = await runAuthWizard(commandCwd, { provider });
1185
1216
  printAuthWizardResult(result);
1186
1217
  return;
1187
1218
  }
1188
1219
  const target = provider || "deepseek";
1189
- const key = argv.includes("--stdin") || !process.stdin.isTTY
1220
+ const key = commandArgv.includes("--stdin") || !process.stdin.isTTY
1190
1221
  ? await readStdin()
1191
1222
  : await promptHidden(`${providerLabel(target)} API key/token: `);
1192
1223
  if (!key) {
1193
1224
  console.error("No key saved.");
1194
1225
  process.exit(1);
1195
1226
  }
1196
- const result = await setProviderKey(process.cwd(), target, key);
1227
+ const result = await setProviderKey(commandCwd, target, key);
1197
1228
  console.log(`saved ${result.provider} key to project-local ignored env (${result.keyName})`);
1198
1229
  return;
1199
1230
  }
1200
1231
 
1201
- if (argv[0] === "sessions") {
1202
- await handleSessionsCommand(argv.slice(1));
1232
+ if (commandArgv[0] === "sessions") {
1233
+ await handleSessionsCommand(commandArgv.slice(1));
1203
1234
  return;
1204
1235
  }
1205
1236
 
1206
- if (argv[0] === "storage") {
1207
- await handleStorageCommand(argv.slice(1));
1237
+ if (commandArgv[0] === "storage") {
1238
+ await handleStorageCommand(commandArgv.slice(1));
1208
1239
  return;
1209
1240
  }
1210
1241
 
1211
- if (argv[0] === "models" || argv[0] === "model") {
1242
+ if (commandArgv[0] === "models" || commandArgv[0] === "model") {
1212
1243
  printModels();
1213
1244
  return;
1214
1245
  }
1215
1246
 
1216
- if (argv[0] === "skills" || argv[0] === "skill") {
1217
- printSkills(argv.slice(1).join(" ").trim());
1247
+ if (commandArgv[0] === "skills" || commandArgv[0] === "skill") {
1248
+ printSkills(commandArgv.slice(1).join(" ").trim());
1218
1249
  return;
1219
1250
  }
1220
1251
 
1221
- if (argv[0] === "queue") {
1222
- await handleQueueCommand(argv.slice(1));
1252
+ if (commandArgv[0] === "queue") {
1253
+ await handleQueueCommand(commandArgv.slice(1));
1223
1254
  return;
1224
1255
  }
1225
1256
 
1226
- if (argv[0] === "resume") {
1227
- const resumeArgv = argv.slice(1);
1257
+ if (commandArgv[0] === "resume") {
1258
+ const resumeArgv = commandArgv.slice(1);
1228
1259
  const allSessions = resumeArgv.includes("--all-sessions");
1229
1260
  const positional = resumeArgv.filter((arg) => arg !== "--all-sessions");
1230
1261
  let sessionId = positional[0] || "";
@@ -1237,20 +1268,20 @@ export async function main(argv = process.argv.slice(2)) {
1237
1268
  }
1238
1269
  if (!sessionId) return;
1239
1270
  if (!prompt) {
1240
- await startInteractiveCli(agentDefaults({ ...parseArgs([]), resume: sessionId }), {
1271
+ await startInteractiveCli(agentDefaults({ ...parseArgs([]), resume: sessionId, commandCwd }), {
1241
1272
  packageDir,
1242
1273
  packageVersion: packageJson.version,
1243
1274
  });
1244
1275
  return;
1245
1276
  }
1246
- const resumeArgs = agentDefaults({ ...parseArgs([prompt]), resume: sessionId, goal: prompt });
1277
+ const resumeArgs = agentDefaults({ ...parseArgs([prompt]), resume: sessionId, goal: prompt, commandCwd });
1247
1278
  if (!(await ensureDeepSeekKeyForOneShot(resumeArgs))) process.exit(1);
1248
1279
  const config = loadConfig(resumeArgs, { packageDir });
1249
1280
  await runAgent(config);
1250
1281
  return;
1251
1282
  }
1252
1283
 
1253
- const args = parseArgs(argv);
1284
+ const args = { ...parseArgs(commandArgv), commandCwd };
1254
1285
 
1255
1286
  if (args.web) {
1256
1287
  if (args.port) process.env.PORT = String(args.port);
@@ -35,6 +35,7 @@ import {
35
35
  } from "./skillmesh.js";
36
36
  import { normalizeScsMode } from "./scs-controller.js";
37
37
  import { formatAapsResult, runAapsAction } from "./aaps-adapter.js";
38
+ import { formatInstructionTemplateList, normalizeInstructionTemplate } from "./behavior-contract.js";
38
39
 
39
40
  const useColor = Boolean(input.isTTY && output.isTTY && process.env.AGINTIFLOW_NO_COLOR !== "1");
40
41
  const ansi = {
@@ -108,6 +109,12 @@ const SLASH_COMMANDS = [
108
109
  "/web",
109
110
  "/exit",
110
111
  ];
112
+
113
+ function parseInitTemplateValue(value = "") {
114
+ const parts = String(value || "").trim().split(/\s+/).filter(Boolean);
115
+ if (parts[0] === "template" || parts[0] === "--template" || parts[0] === "-t") return normalizeInstructionTemplate(parts[1]);
116
+ return normalizeInstructionTemplate(parts[0] || "disciplined");
117
+ }
111
118
  const promptHistory = [];
112
119
  let activeRunInput = null;
113
120
  let cliLanguage = resolveLanguage();
@@ -2565,9 +2572,14 @@ async function handleCommand(line, state, packageDir) {
2565
2572
  return true;
2566
2573
  }
2567
2574
  if (command === "instructions" || command === "memory") {
2568
- if (value === "init") {
2569
- const result = await initProject(process.cwd());
2570
- printAgentMessage(`AGINTI.md ready at ${result.instructionsPath}`);
2575
+ if (value === "templates") {
2576
+ printAgentMessage(`Available AGINTI.md templates:\n${formatInstructionTemplateList()}`);
2577
+ return true;
2578
+ }
2579
+ if (value === "init" || value.startsWith("init ")) {
2580
+ const template = parseInitTemplateValue(value.replace(/^init\b\s*/, ""));
2581
+ const result = await initProject(process.cwd(), { template });
2582
+ printAgentMessage(`AGINTI.md ready at ${result.instructionsPath}\ntemplate=${result.template}`);
2571
2583
  return true;
2572
2584
  }
2573
2585
  const instructions = await readProjectInstructions(process.cwd(), { maxBytes: 4000 });
@@ -3079,8 +3091,13 @@ async function handleCommand(line, state, packageDir) {
3079
3091
  return true;
3080
3092
  }
3081
3093
  if (command === "init") {
3082
- const result = await initProject(process.cwd());
3083
- printAgentMessage(`initialized project=${result.projectRoot}\nAGINTI.md=${result.instructionsPath}`);
3094
+ if (value === "templates" || value === "list") {
3095
+ printAgentMessage(`Available AGINTI.md templates:\n${formatInstructionTemplateList()}`);
3096
+ return true;
3097
+ }
3098
+ const template = parseInitTemplateValue(value);
3099
+ const result = await initProject(process.cwd(), { template });
3100
+ printAgentMessage(`initialized project=${result.projectRoot}\nAGINTI.md=${result.instructionsPath}\ntemplate=${result.template}`);
3084
3101
  return true;
3085
3102
  }
3086
3103
  if (command === "web") {
@@ -5,6 +5,7 @@ import { listAuxiliarySkills } from "./auxiliary-tools.js";
5
5
  import { engineeringGuidanceForTask } from "./engineering-guidance.js";
6
6
  import { formatSkillsForPrompt, selectSkillsForGoal } from "./skill-library.js";
7
7
  import { platformInfo, platformLabel } from "./platform.js";
8
+ import { formatBehaviorContractForPrompt } from "./behavior-contract.js";
8
9
 
9
10
  export function createClient(config) {
10
11
  if (config.provider === "mock") {
@@ -498,7 +499,7 @@ export async function createPlan(client, config, state) {
498
499
  {
499
500
  role: "system",
500
501
  content:
501
- "You are planning a browser, shell, workspace, and coding-agent task. The plan is only a launchpad: after planning, the runtime will continue with tools until the task is complete or genuinely blocked. Prefer real workspace edits/checks over advice-only answers. If a local shell command can satisfy the goal, prefer that before browser actions. Treat any suggested start URL as optional. Write a concise execution plan with 3 to 6 steps. Mention risks or blockers when relevant. Keep it short and practical.",
502
+ `You are planning a browser, shell, workspace, and coding-agent task. The plan is only a launchpad: after planning, the runtime will continue with tools until the task is complete or genuinely blocked. Prefer real workspace edits/checks over advice-only answers. If a local shell command can satisfy the goal, prefer that before browser actions. Treat any suggested start URL as optional. ${formatBehaviorContractForPrompt({ mode: "plan" })} Write a concise execution plan with 3 to 6 steps. Mention risks or blockers when relevant. Keep it short and practical.`,
502
503
  },
503
504
  {
504
505
  role: "user",
package/src/project.js CHANGED
@@ -6,6 +6,7 @@ import { promisify } from "node:util";
6
6
  import { listAgentWrappers } from "./tool-wrappers.js";
7
7
  import { getDockerSandboxStatus } from "./docker-sandbox.js";
8
8
  import { platformInfo, platformLabel, platformSetupHints } from "./platform.js";
9
+ import { buildAgintiInstructions, normalizeInstructionTemplate } from "./behavior-contract.js";
9
10
  import {
10
11
  LEGACY_PROJECT_SESSIONS_DIR_NAME,
11
12
  PROJECT_SESSIONS_DIR_NAME,
@@ -157,35 +158,8 @@ export async function ensureProjectSessionStorage(projectRoot = process.cwd()) {
157
158
  return paths;
158
159
  }
159
160
 
160
- export function defaultAgintiInstructions() {
161
- return [
162
- "# AGINTI.md",
163
- "",
164
- "Project instructions for AgInTiFlow agents.",
165
- "",
166
- "Edit this file directly or ask AgInTiFlow to update it during chat. Keep durable project preferences here; keep secrets in `.aginti/.env` instead.",
167
- "",
168
- "## Project Goals",
169
- "",
170
- "- Describe what this project is for.",
171
- "- Note the main user-facing workflows the agent should preserve.",
172
- "",
173
- "## Agent Preferences",
174
- "",
175
- "- Prefer small, inspectable changes over broad rewrites.",
176
- "- Read relevant files before editing.",
177
- "- Run focused checks when tools and dependencies are available.",
178
- "- Keep generated artifacts in project folders with clear names.",
179
- "",
180
- "## Useful Commands",
181
- "",
182
- "- Add build, test, lint, preview, or compile commands here.",
183
- "",
184
- "## Notes",
185
- "",
186
- "- Add project-specific terminology, style preferences, and known constraints here.",
187
- "",
188
- ].join("\n");
161
+ export function defaultAgintiInstructions(template = "disciplined") {
162
+ return buildAgintiInstructions(template);
189
163
  }
190
164
 
191
165
  export async function readProjectInstructions(projectRoot = process.cwd(), { maxBytes = 24_000 } = {}) {
@@ -272,8 +246,9 @@ async function ensureLine(filePath, lines) {
272
246
  return { changed: true, path: filePath, added: missing };
273
247
  }
274
248
 
275
- export async function initProject(projectRoot = process.cwd()) {
249
+ export async function initProject(projectRoot = process.cwd(), { template = "disciplined" } = {}) {
276
250
  const paths = projectPaths(projectRoot);
251
+ const instructionTemplate = normalizeInstructionTemplate(template);
277
252
  const created = [];
278
253
  const updated = [];
279
254
  const skipped = [];
@@ -300,7 +275,7 @@ export async function initProject(projectRoot = process.cwd()) {
300
275
  await fsp.mkdir(paths.globalSessionsDir, { recursive: true });
301
276
  await ensureFile(
302
277
  paths.agintiInstructionsPath,
303
- defaultAgintiInstructions()
278
+ defaultAgintiInstructions(instructionTemplate)
304
279
  );
305
280
  await ensureFile(
306
281
  paths.controlReadmePath,
@@ -370,6 +345,7 @@ export async function initProject(projectRoot = process.cwd()) {
370
345
  created,
371
346
  updated,
372
347
  skipped,
348
+ template: instructionTemplate,
373
349
  };
374
350
  }
375
351
 
@@ -1,4 +1,5 @@
1
1
  import { redactSensitiveText, redactValue } from "./redaction.js";
2
+ import { formatBehaviorContractForPrompt, scsContractCriteria } from "./behavior-contract.js";
2
3
 
3
4
  export const SCS_MODES = ["off", "on", "auto"];
4
5
 
@@ -67,9 +68,10 @@ export function shouldActivateScs(mode = "off", context = {}) {
67
68
  function fallbackPlan(goal = "") {
68
69
  return [
69
70
  "1. Inspect the workspace, project instructions, and relevant manifests before editing.",
70
- "2. Make the smallest coherent implementation or research pass that satisfies the request.",
71
- "3. Run targeted checks or document why checks are unavailable.",
72
- "4. Finish only after concrete evidence supports the result.",
71
+ "2. State assumptions or ambiguities that could change scope, safety, or implementation.",
72
+ "3. Make the smallest coherent implementation or research pass that satisfies the request.",
73
+ "4. Run targeted checks or document why checks are unavailable.",
74
+ "5. Finish only after concrete evidence supports the result.",
73
75
  ].join("\n");
74
76
  }
75
77
 
@@ -207,6 +209,7 @@ export function buildSupervisorInstruction(scs = {}) {
207
209
  return [
208
210
  "SCS mode is enabled. You are the supervisor executor.",
209
211
  "Execute the approved phase plan. You may choose exact tools and paths, but you may not replace the strategic plan with a new one.",
212
+ formatBehaviorContractForPrompt(),
210
213
  "If tool evidence invalidates the plan, stop repeating the failed path and explain the blocker through finish or wait for student review.",
211
214
  "Approved phase plan:",
212
215
  scs.plan || fallbackPlan(),
@@ -225,6 +228,7 @@ function normalizeCommitteePlan(parsed, goal = "") {
225
228
  phaseGoal: compact(parsed.phase_goal || parsed.phaseGoal || goal || "Complete the requested task.", 260),
226
229
  plan,
227
230
  acceptanceCriteria: normalizeStringList(parsed.acceptance_criteria || parsed.acceptanceCriteria, [
231
+ ...scsContractCriteria(),
228
232
  "The requested outcome is present in the workspace or answer.",
229
233
  "Relevant checks were run or skipped with a concrete reason.",
230
234
  ]),
@@ -280,7 +284,7 @@ export async function createScsPlan(client, config, state, context = {}) {
280
284
  {
281
285
  role: "system",
282
286
  content:
283
- "You are the SCS committee. Draft one practical next-phase plan only. You cannot approve it and you cannot call tools. Return strict JSON with keys: role, phase_goal, plan, acceptance_criteria, allowed_tools, stop_conditions.",
287
+ `You are the SCS committee. Draft one practical next-phase plan only. You cannot approve it and you cannot call tools. ${formatBehaviorContractForPrompt({ mode: "plan" })} Return strict JSON with keys: role, phase_goal, plan, acceptance_criteria, allowed_tools, stop_conditions.`,
284
288
  },
285
289
  {
286
290
  role: "user",
@@ -299,7 +303,7 @@ export async function createScsPlan(client, config, state, context = {}) {
299
303
  {
300
304
  role: "system",
301
305
  content:
302
- "You are the SCS student monitor. You may approve_plan or veto_plan only. Judge whether the committee phase plan is safe, concrete, and evidence-oriented. Return strict JSON with keys: role, decision, confidence, evidence, reason, next_required_action.",
306
+ `You are the SCS student monitor. You may approve_plan or veto_plan only. Judge whether the committee phase plan is safe, scoped, minimal, permission-aware, and evidence-oriented. ${formatBehaviorContractForPrompt({ mode: "plan" })} Return strict JSON with keys: role, decision, confidence, evidence, reason, next_required_action.`,
303
307
  },
304
308
  {
305
309
  role: "user",