@multileaf/ai-workflow 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,26 @@ All notable changes to this project are documented in this file. The format foll
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.3.0] - 2026-10-08
8
+
9
+ ### Added
10
+
11
+ - Optional semantic mapping in `aiw scan`: when `.aiw/semantic.yml` enables a local embedding model (for example Ollama `embeddinggemma-2`), the scan verifies model availability, embeds line-aligned file chunks, and writes a versioned index to `.aiw/semantic/index.json`.
12
+ - `aiw semantic query` and `aiw semantic status` commands for cosine-similarity retrieval over the semantic index, gated by the same configuration flag.
13
+ - Advisory decision triage via a local decision model (`jev/clef-flash` through Ollama `/v1/systemone`): flag-gated by `.aiw/decision.yml`, `aiw decision status` and `aiw decision triage --artifact=<path>` return advisory evidence-completeness and gate decisions with confidence, never replacing human approval.
14
+ - Documentation for semantic mapping and an evaluation of the `jev/clef` decision model for advisory flow decisions.
15
+
16
+ ### Fixed
17
+
18
+ - Removed high-severity transitive vulnerabilities (`brace-expansion`, `source-map-js`) reported by `npm audit`.
19
+
20
+ ## [0.2.1] - 2026-09-13
21
+
22
+ ### Changed
23
+
24
+ - Require host-native blocking question widgets for onboarding decisions and every SDD approval gate when available, with explicit unselected choices and a safe chat fallback.
25
+ - Clarify that the CLI persists approval state but cannot prove that a host widget was used or verify respondent identity.
26
+
7
27
  ## [0.2.0] - 2026-09-13
8
28
 
9
29
  ### Added
package/README.md CHANGED
@@ -230,7 +230,7 @@ npx @multileaf/ai-workflow approve traceability
230
230
  npx @multileaf/ai-workflow workflow complete
231
231
  ```
232
232
 
233
- Scaffold commands write deterministic files at fixed paths and a repeated invocation replaces that scaffold. Each applicable workflow stage requires two separate checks: its automated quality gate (when available), then explicit human approval. After presenting the artifact or evidence, the agent must stop and wait for the human before invoking the next skill, command, scaffold, or implementation action. A passing `aiw gate` validates artifact completeness only; it never records or implies human approval. Requirements use stable identifiers and Given/When/Then acceptance criteria. Plans link tasks to requirements, tests, risks, dependencies, and expected evidence. `verify` checks that the specification has acceptance criteria and the plan links requirements to validation; it does not execute project test commands. `trace` records declared links between requirements, decisions, tasks, code, tests, and evidence. Code review is performed with the `code-review` skill when installed; there is no separate review gate command, but the human must accept the review outcome before completion.
233
+ Scaffold commands write deterministic files at fixed paths and a repeated invocation replaces that scaffold. Each applicable workflow stage requires two separate checks: its automated quality gate (when available), then explicit human approval. When the host provides a blocking question widget, the agent must use it with unselected approve, request-changes, and reject choices; it must wait for an actual submission before invoking the next skill, command, scaffold, or implementation action. A numbered chat prompt is only the fallback when no blocking widget exists. A passing `aiw gate` validates artifact completeness only; it never records or implies human approval. Requirements use stable identifiers and Given/When/Then acceptance criteria. Plans link tasks to requirements, tests, risks, dependencies, and expected evidence. `verify` checks that the specification has acceptance criteria and the plan links requirements to validation; it does not execute project test commands. `trace` records declared links between requirements, decisions, tasks, code, tests, and evidence. Code review is performed with the `code-review` skill when installed; there is no separate review gate command, but the human must accept the review outcome before completion.
234
234
 
235
235
  ## Project intelligence
236
236
 
package/dist/adapter.js CHANGED
@@ -42,14 +42,16 @@ This skill is the bootstrap for AI Workflow. The installer intentionally install
42
42
 
43
43
  ### Mandatory interaction checkpoints
44
44
 
45
- This setup is gated by explicit user replies. A question is a blocking checkpoint, not narration: ask it, end your current response/turn immediately, and do no further analysis, tool calls, commands, or file changes until the user replies in a later turn. Never answer your own question, infer an answer from silence, or continue because a widget has a preselected value. If the host cannot pause for an answer, stop and ask the user to continue the setup after replying.
45
+ This setup is gated by explicit user replies. At every human decision, call the host's native blocking question widget/tool; do not replace it with a prose question when the widget is available. Use the selection widget for multi-select decisions. Leave every choice unselected by default. Do not continue, call tools, run commands, or change files while a widget is awaiting submission. Only its actual submitted response is consent; never answer your own question or infer consent from silence, ambiguity, cancellation, or a preselected value. If the host has no blocking question widget, ask one concise numbered question in chat, end the turn, and wait for an explicit reply in a later turn. This is an interaction requirement for the agent; the AI Workflow CLI cannot independently verify which UI produced an approval.
46
46
 
47
- 1. Before scanning, recommend that the user switch to a cheaper model. Ask whether they switched or explicitly want to continue with the current model; never change model settings yourself. Explain that analysis is read-only and optional resources will not be installed without their selection. Then stop the turn and wait. Do not launch analysis agents or run \`aiw scan\` until the user replies. If the reply is unclear, ask again and stop; proceed only after an explicit answer to continue (including an explicit choice to keep the current model).
47
+ Use that widget for the model choice, confirmation of uncertain scan findings, all/custom/none recommendation selection, approval of the exact sync preview, external network or remote-skill consent, and each separate MCP configuration/execution/credential permission. For a single decision, offer explicit choices with no default; for “request changes,” “correct this finding,” or “other,” collect the user's free-text details in a follow-up widget before acting.
48
+
49
+ 1. Before scanning, recommend that the user switch to a cheaper model. Use a blocking question widget with no default choice to offer “I switched; continue”, “Keep this model; continue”, or “Stop setup”; never change model settings yourself. Explain that analysis is read-only and optional resources will not be installed without their selection. Wait for and evaluate the widget’s explicit response. Do not launch analysis agents or run \`aiw scan\` until the widget returns an explicit continue choice. If the widget is dismissed or the reply is unclear, ask again with the widget and stop; proceed only after an explicit answer to continue (including an explicit choice to keep the current model).
48
50
  2. Use the host's native subagent mechanism to launch these read-only analyses in parallel: (a) technologies, root and nested manifests, workspaces, and monorepo boundaries; (b) architecture, modules, entry points, and recurring code patterns; (c) tests, quality commands, CI, and developer tools. Keep the main agent as orchestrator. Wait for all results, verify important claims against cited project files, reconcile disagreements, and consolidate one evidence-backed project profile. Do not inspect secrets, ignored files, dependency directories such as \`node_modules\`, or generated output; inspect declared dependencies in project manifests. If subagents are unavailable, perform the same analyses directly and disclose the fallback.
49
51
  3. Run \`npx --yes --package=@multileaf/ai-workflow -- aiw scan\` and read \`.aiw/profile.yml\`. Treat this deterministic scan as another evidence source, not as a replacement for workspace-level analysis. Summarize each workspace separately; do not assume root dependencies or commands apply to every module. Ask the user to confirm uncertain or conflicting facts before using them as project policy. Add confirmed architecture and recurring patterns to the matching entries in the \`project_modules\` JSON array in \`.aiw/profile.yml\`, including file-path evidence; create a root-level entry when only the root project exists. Preserve existing detected fields and never store secrets or source-code excerpts there.
50
52
  4. Run \`npx --yes --package=@multileaf/ai-workflow -- aiw recommend\` and read \`.aiw/recommendations.yml\`. Combine catalog results with the consolidated project profile. Recommend matching skills, rules, agents, hooks, templates, and MCP servers. For every MCP, disclose its publisher/source, purpose, capabilities, permissions, transport, and credential needs; verify them against current official host and server documentation. Recommend only integrations supported by project evidence and never invent credentials.
51
53
  5. Present recommendations as concise tables grouped by category: skills, rules, agents, hooks, templates, and MCPs. Include one row per recommended item and columns for the item, a short project-specific reason for the recommendation, and relevant evidence. For MCP rows, also summarize purpose, source, permissions, transport, and credential needs (use a separate table if needed). Do not show a flat list without category headings or generic reasons such as “useful for this project.” After the tables, use the host's native selection widget to offer three explicit choices: install all recommendations, customize, or install none. The widget must start with no choice selected; never accept a default or preselected value as the user's answer. If the user customizes, show selectable category blocks and individual items, keeping each item's short reason and declared prerequisites visible. Read \`requires\` from \`.aiw/recommendations.yml\`; explain that choosing a stage auto-selects its prerequisite capabilities and their resources. If the host lacks a multi-select widget, ask a numbered all/custom/none question and stop the turn to wait for the reply. Do not run a selection command or install anything until a reply/explicit widget submission arrives in a later turn. A dismissed widget, empty answer, silence, or ambiguous answer means no consent; ask again or stop without installing. MCP selection does not grant permission to execute the server or use external access; ask separately for those permissions.
52
- 6. Only after receiving the user's explicit all/custom/none choice, record it: use \`npx --yes --package=@multileaf/ai-workflow -- aiw recommend --select=all\` for an explicit all choice; for customized item-level choices, pass only the selected resource references such as \`skills/verification,rules/tdd-policy,agents/test-engineer\`; use \`npx --yes --package=@multileaf/ai-workflow -- aiw recommend --select=\` for an explicit none choice. Before syncing a non-empty selection, show the exact selected resources and their prerequisites. Run \`npx --yes --package=@multileaf/ai-workflow -- aiw sync --preview\` and show the complete plan, including additions, updates, preserved files, conflicts, and any external permissions. Ask the user to approve this exact plan and stop the turn. Run sync only after that confirmation arrives in a later turn. Only after a later explicit approval, rerun preview if selection or project files changed, then run \`npx --yes --package=@multileaf/ai-workflow -- aiw sync --approve-plan=<fingerprint>\` using the fingerprint from the approved preview. A changed plan or conflict must stop without writes. Remote skills are not installed by sync: ask separately for network access and installation approval, then use \`npx --yes --package=@multileaf/ai-workflow -- aiw skills install --source=vercel-labs/agent-skills --skill=vercel-react-best-practices --allow=network:external\`. MCP configuration, server execution, and credentials each require separate consent. Install only confirmed selected resources and declared dependencies. Tailor instructions only where project evidence changes their usefulness (such as test commands, workspace boundaries, framework patterns, or module architecture). Preserve existing files, configuration, and secrets; report conflicts rather than overwriting them.
54
+ 6. Only after receiving the user's explicit all/custom/none choice, record it: use \`npx --yes --package=@multileaf/ai-workflow -- aiw recommend --select=all\` for an explicit all choice; for customized item-level choices, pass only the selected resource references such as \`skills/verification,rules/tdd-policy,agents/test-engineer\`; use \`npx --yes --package=@multileaf/ai-workflow -- aiw recommend --select=\` for an explicit none choice. Before syncing a non-empty selection, show the exact selected resources and their prerequisites. Run \`npx --yes --package=@multileaf/ai-workflow -- aiw sync --preview\` and show the complete plan, including additions, updates, preserved files, conflicts, and any external permissions. Use the blocking question widget to approve this exact plan, request changes, or cancel. Run sync only after that confirmation arrives from an explicit widget submission. Only after a later explicit approval, rerun preview if selection or project files changed, then run \`npx --yes --package=@multileaf/ai-workflow -- aiw sync --approve-plan=<fingerprint>\` using the fingerprint from the approved preview. A changed plan or conflict must stop without writes. Remote skills are not installed by sync: ask separately for network access and installation approval, then use \`npx --yes --package=@multileaf/ai-workflow -- aiw skills install --source=vercel-labs/agent-skills --skill=vercel-react-best-practices --allow=network:external\`. MCP configuration, server execution, and credentials each require separate consent. Install only confirmed selected resources and declared dependencies. Tailor instructions only where project evidence changes their usefulness (such as test commands, workspace boundaries, framework patterns, or module architecture). Preserve existing files, configuration, and secrets; report conflicts rather than overwriting them.
53
55
  7. For selected MCPs, add only the selected servers to the host's supported project configuration. Check current official host and server documentation for the configuration format, merge existing settings without replacing unrelated entries, and use environment-variable references instead of literal credentials. Do not run server install/start/authentication commands without separate approval. If the target requires user-level changes or safe configuration cannot be validated, give the user exact manual steps and report the MCP as pending rather than installed.
54
56
  8. Report installed resources by block, project-specific changes, skipped recommendations, MCP trust/authentication steps, and conflicts. Recommend restarting or reloading the coding-agent session so it discovers newly installed resources; follow known host behavior if it reloads changes automatically. Introduce only the gates supported by installed resources: brainstorming → specification → architecture decision when needed → implementation plan → implementation with tests → verification → code review → traceability.
55
57
 
@@ -59,7 +61,9 @@ If the user declines all recommendations, leave the project with only the bootst
59
61
 
60
62
  ### Mandatory human gates between stages
61
63
 
62
- For every CLI command below, use \`npx --yes --package=@multileaf/ai-workflow -- aiw <command>\`; do not assume a global \`aiw\` binary exists. Start each feature request with \`npx --yes --package=@multileaf/ai-workflow -- aiw workflow start\` and check progress with \`npx --yes --package=@multileaf/ai-workflow -- aiw workflow status\`. Complete it with \`npx --yes --package=@multileaf/ai-workflow -- aiw workflow complete\` only after all stages resolve. The \`aiw gate <stage>\` command checks evidence and records a pending review; a passing result is not human approval. At every stage boundary—brainstorming, specification, technical design, plan, implementation, verification, code review, and traceability—present the exact artifact/evidence, ask whether the user approves proceeding, end the turn, and wait for an explicit reply in a later turn. On approval, record it with \`npx --yes --package=@multileaf/ai-workflow -- aiw approve <stage>\`; if the user asks for changes, record the rejection first with \`npx --yes --package=@multileaf/ai-workflow -- aiw reject <stage> --reason=<feedback>\`, then revise and rerun the gate. Do not invoke the next skill, create its scaffold, run its commands, or edit its artifacts before approval is recorded or the pending stage is rejected. Silence, an ambiguous reply, an automated gate pass, or earlier approval never counts. Brainstorming and technical design may be skipped only after explaining why and receiving explicit human acceptance; record each skip with \`npx --yes --package=@multileaf/ai-workflow -- aiw skip <stage> --reason=<accepted reason>\`.
64
+ At every stage gate, use the host's native blocking question widget and present the exact evidence with these unselected choices: “Approve and continue”, “Request changes”, and “Reject/stop”. For optional brainstorming or technical-design skips, use a separate widget with “Accept this skip” and “Do not skip”; collect the reason before recording an accepted skip. Do not run \`aiw approve\`, \`aiw reject\`, or \`aiw skip\` until the relevant widget has returned an explicit submission. A request for changes must collect the feedback in a follow-up question widget, record rejection, then revise and rerun the gate. If no blocking question widget exists, use a numbered chat prompt and end the turn until an explicit reply arrives. A widget submission is the agent's required interaction signal; the CLI cannot prove the widget was used.
65
+
66
+ For every CLI command below, use \`npx --yes --package=@multileaf/ai-workflow -- aiw <command>\`; do not assume a global \`aiw\` binary exists. Start each feature request with \`npx --yes --package=@multileaf/ai-workflow -- aiw workflow start\` and check progress with \`npx --yes --package=@multileaf/ai-workflow -- aiw workflow status\`. Complete it with \`npx --yes --package=@multileaf/ai-workflow -- aiw workflow complete\` only after all stages resolve. The \`aiw gate <stage>\` command checks evidence and records a pending review; a passing result is not human approval. At every stage boundary—brainstorming, specification, technical design, plan, implementation, verification, code review, and traceability—present the exact artifact/evidence, ask using the blocking question widget and wait for its explicit submission before proceeding; end the turn only when the widget is unavailable. On approval, record it with \`npx --yes --package=@multileaf/ai-workflow -- aiw approve <stage>\`; if the user asks for changes, record the rejection first with \`npx --yes --package=@multileaf/ai-workflow -- aiw reject <stage> --reason=<feedback>\`, then revise and rerun the gate. Do not invoke the next skill, create its scaffold, run its commands, or edit its artifacts before approval is recorded or the pending stage is rejected. Silence, an ambiguous reply, an automated gate pass, or earlier approval never counts. Brainstorming and technical design may be skipped only after explaining why and receiving explicit human acceptance; record each skip with \`npx --yes --package=@multileaf/ai-workflow -- aiw skip <stage> --reason=<accepted reason>\`.
63
67
 
64
68
  - Use the brainstorming skill when scope, users, constraints, or assumptions are unclear; create its artifact with \`npx --yes --package=@multileaf/ai-workflow -- aiw brainstorm\`, validate it with \`npx --yes --package=@multileaf/ai-workflow -- aiw gate brainstorming\`, present it, wait, then record the reply with \`npx --yes --package=@multileaf/ai-workflow -- aiw approve brainstorming\` before specification. If unnecessary, ask to skip and record explicit acceptance with \`npx --yes --package=@multileaf/ai-workflow -- aiw skip brainstorming --reason=...\`.
65
69
  - Create a specification with stable requirement IDs and Given/When/Then acceptance criteria; pass \`npx --yes --package=@multileaf/ai-workflow -- aiw gate specification\`, present it, wait, and record approval before design or planning.
@@ -0,0 +1,49 @@
1
+ import type { FileSystem } from "./types.js";
2
+ export type DecisionConfig = {
3
+ enabled: boolean;
4
+ model: string;
5
+ baseUrl: string;
6
+ };
7
+ export type DecisionQuestion = {
8
+ type: "noul";
9
+ instructions: string;
10
+ criteria?: {
11
+ true: string;
12
+ false: string;
13
+ };
14
+ } | {
15
+ type: "choice";
16
+ instructions: string;
17
+ criteria: Record<string, string | null>;
18
+ } | {
19
+ type: "score";
20
+ instructions: string;
21
+ criteria: string[];
22
+ };
23
+ export type DecisionAnswer = {
24
+ type: "noul" | "choice" | "score";
25
+ noul?: number;
26
+ choice?: string;
27
+ score?: number;
28
+ probabilities?: Record<string, number>;
29
+ confidence?: number;
30
+ };
31
+ export type DecisionRequest = {
32
+ state: string;
33
+ questions: Record<string, DecisionQuestion>;
34
+ };
35
+ export type DecisionResponse = {
36
+ model: string;
37
+ answers: Record<string, DecisionAnswer>;
38
+ };
39
+ export type DecisionProvider = {
40
+ available(): Promise<boolean>;
41
+ decide(request: DecisionRequest): Promise<DecisionResponse>;
42
+ };
43
+ export declare const DECISION_CONFIG_PATH = "decision.yml";
44
+ export declare const DEFAULT_DECISION_CONFIG: DecisionConfig;
45
+ export declare function parseDecisionConfig(text: string): DecisionConfig;
46
+ export declare function loadDecisionConfig(fs: FileSystem, aiwPath: string): DecisionConfig | undefined;
47
+ export declare function parseDecisionResponse(text: string): DecisionResponse;
48
+ export declare function createOllamaDecisionProvider(config: DecisionConfig): DecisionProvider;
49
+ export declare function buildEvidenceTriageRequest(artifact: string): DecisionRequest;
@@ -0,0 +1,105 @@
1
+ import { parseDocument } from "yaml";
2
+ export const DECISION_CONFIG_PATH = "decision.yml";
3
+ export const DEFAULT_DECISION_CONFIG = {
4
+ enabled: false,
5
+ model: "clef-flash:latest",
6
+ baseUrl: "http://localhost:11434",
7
+ };
8
+ export function parseDecisionConfig(text) {
9
+ const document = parseDocument(text);
10
+ const node = document.get("decision");
11
+ if (document.errors.length > 0 || !node || typeof node !== "object")
12
+ throw new Error("Decision configuration must be a YAML object with a `decision` mapping");
13
+ const raw = node.toJSON();
14
+ const enabled = raw.enabled;
15
+ const model = raw.model;
16
+ const baseUrl = raw.baseUrl;
17
+ if (typeof enabled !== "boolean")
18
+ throw new Error("decision.enabled must be a boolean");
19
+ if (!enabled)
20
+ return { ...DEFAULT_DECISION_CONFIG, enabled: false };
21
+ if (typeof model !== "string" || model.trim() === "")
22
+ throw new Error("decision.model must be a non-empty string when decision mapping is enabled");
23
+ if (typeof baseUrl !== "string" || !/^https?:\/\//i.test(baseUrl))
24
+ throw new Error("decision.baseUrl must be an http(s) URL when decision mapping is enabled");
25
+ return { enabled: true, model, baseUrl };
26
+ }
27
+ export function loadDecisionConfig(fs, aiwPath) {
28
+ const configPath = `${aiwPath}/${DECISION_CONFIG_PATH}`;
29
+ if (!fs.exists(configPath))
30
+ return undefined;
31
+ return parseDecisionConfig(fs.read(configPath));
32
+ }
33
+ function parseAnswer(value) {
34
+ if (!value || typeof value !== "object")
35
+ throw new Error("Invalid decision answer");
36
+ const raw = value;
37
+ if (raw.type !== "noul" && raw.type !== "choice" && raw.type !== "score")
38
+ throw new Error("Invalid decision answer type");
39
+ return { ...raw, type: raw.type };
40
+ }
41
+ export function parseDecisionResponse(text) {
42
+ const parsed = JSON.parse(text);
43
+ const answers = parsed.answers;
44
+ if (typeof parsed.model !== "string" || !answers || typeof answers !== "object")
45
+ throw new Error("Invalid decision response");
46
+ const result = {};
47
+ for (const [key, value] of Object.entries(answers))
48
+ result[key] = parseAnswer(value);
49
+ return { model: parsed.model, answers: result };
50
+ }
51
+ export function createOllamaDecisionProvider(config) {
52
+ return {
53
+ async available() {
54
+ try {
55
+ const response = await fetch(`${config.baseUrl}/api/tags`);
56
+ if (!response.ok)
57
+ return false;
58
+ const body = (await response.json());
59
+ const names = new Set((body.models ?? []).flatMap((entry) => typeof entry?.name === "string" && typeof entry?.model === "string"
60
+ ? [entry.name, entry.model]
61
+ : []));
62
+ return names.has(config.model);
63
+ }
64
+ catch {
65
+ return false;
66
+ }
67
+ },
68
+ async decide(request) {
69
+ const questionCount = Object.keys(request.questions).length;
70
+ if (questionCount === 0 || questionCount > 64)
71
+ throw new Error("Decision requests must contain between 1 and 64 questions");
72
+ const response = await fetch(`${config.baseUrl}/v1/systemone`, {
73
+ method: "POST",
74
+ headers: { "content-type": "application/json" },
75
+ body: JSON.stringify({
76
+ model: config.model,
77
+ state: request.state,
78
+ questions: request.questions,
79
+ }),
80
+ });
81
+ if (!response.ok)
82
+ throw new Error(`Decision request failed for model ${config.model}: ${await response.text()}`);
83
+ return parseDecisionResponse(await response.text());
84
+ },
85
+ };
86
+ }
87
+ export function buildEvidenceTriageRequest(artifact) {
88
+ return {
89
+ state: artifact,
90
+ questions: {
91
+ evidence_complete: {
92
+ type: "noul",
93
+ instructions: "Does this artifact include concrete verification evidence for every stated requirement?",
94
+ },
95
+ gate: {
96
+ type: "choice",
97
+ instructions: "Should this artifact advance to human review?",
98
+ criteria: {
99
+ ready: "Every requirement has matching evidence and validation commands",
100
+ "needs-work": "Evidence is missing, incomplete, or does not cover all requirements",
101
+ },
102
+ },
103
+ },
104
+ };
105
+ }
@@ -0,0 +1,39 @@
1
+ import type { FileSystem } from "./types.js";
2
+ export type SemanticConfig = {
3
+ enabled: boolean;
4
+ model: string;
5
+ baseUrl: string;
6
+ chunkChars: number;
7
+ topK: number;
8
+ };
9
+ export type SemanticIndexChunk = {
10
+ file: string;
11
+ text: string;
12
+ vector: number[];
13
+ };
14
+ export type SemanticIndex = {
15
+ schema: 1;
16
+ model: string;
17
+ chunks: SemanticIndexChunk[];
18
+ };
19
+ export type SemanticMatch = {
20
+ file: string;
21
+ score: number;
22
+ excerpt: string;
23
+ };
24
+ export type EmbeddingProvider = {
25
+ available(): Promise<boolean>;
26
+ embed(texts: string[]): Promise<number[][]>;
27
+ };
28
+ export declare const SEMANTIC_CONFIG_PATH = "semantic.yml";
29
+ export declare const SEMANTIC_INDEX_PATH = "semantic/index.json";
30
+ export declare const DEFAULT_SEMANTIC_CONFIG: SemanticConfig;
31
+ export declare function parseSemanticConfig(text: string): SemanticConfig;
32
+ export declare function loadSemanticConfig(fs: FileSystem, aiwPath: string): SemanticConfig | undefined;
33
+ export declare function createOllamaEmbeddingProvider(config: SemanticConfig): EmbeddingProvider;
34
+ export declare function chunkText(content: string, chunkChars: number): string[];
35
+ export declare function cosineSimilarity(a: number[], b: number[]): number;
36
+ export declare function buildSemanticIndex(files: string[], readContent: (file: string) => Promise<string | undefined>, provider: EmbeddingProvider, config: SemanticConfig): Promise<SemanticIndex>;
37
+ export declare function serializeSemanticIndex(index: SemanticIndex): string;
38
+ export declare function parseSemanticIndex(text: string): SemanticIndex;
39
+ export declare function querySemanticIndex(index: SemanticIndex, query: string, provider: EmbeddingProvider, config: SemanticConfig): Promise<SemanticMatch[]>;
@@ -0,0 +1,160 @@
1
+ import { parseDocument } from "yaml";
2
+ export const SEMANTIC_CONFIG_PATH = "semantic.yml";
3
+ export const SEMANTIC_INDEX_PATH = "semantic/index.json";
4
+ export const DEFAULT_SEMANTIC_CONFIG = {
5
+ enabled: false,
6
+ model: "embeddinggemma-2:latest",
7
+ baseUrl: "http://localhost:11434",
8
+ chunkChars: 1200,
9
+ topK: 8,
10
+ };
11
+ export function parseSemanticConfig(text) {
12
+ const document = parseDocument(text);
13
+ const node = document.get("semantic");
14
+ if (document.errors.length > 0 || !node || typeof node !== "object")
15
+ throw new Error("Semantic configuration must be a YAML object with a `semantic` mapping");
16
+ const raw = node.toJSON();
17
+ const enabled = raw.enabled;
18
+ const model = raw.model;
19
+ const baseUrl = raw.baseUrl;
20
+ const chunkChars = raw.chunkChars;
21
+ const topK = raw.topK;
22
+ if (typeof enabled !== "boolean")
23
+ throw new Error("semantic.enabled must be a boolean");
24
+ if (!enabled)
25
+ return { ...DEFAULT_SEMANTIC_CONFIG, enabled: false };
26
+ if (typeof model !== "string" || model.trim() === "")
27
+ throw new Error("semantic.model must be a non-empty string when semantic mapping is enabled");
28
+ if (typeof baseUrl !== "string" || !/^https?:\/\//i.test(baseUrl))
29
+ throw new Error("semantic.baseUrl must be an http(s) URL when semantic mapping is enabled");
30
+ const parsedChunk = chunkChars === undefined ? DEFAULT_SEMANTIC_CONFIG.chunkChars : chunkChars;
31
+ const parsedTopK = topK === undefined ? DEFAULT_SEMANTIC_CONFIG.topK : topK;
32
+ if (typeof parsedChunk !== "number" || !Number.isInteger(parsedChunk) || parsedChunk < 200)
33
+ throw new Error("semantic.chunkChars must be an integer of at least 200");
34
+ if (typeof parsedTopK !== "number" || !Number.isInteger(parsedTopK) || parsedTopK < 1)
35
+ throw new Error("semantic.topK must be a positive integer");
36
+ return { enabled: true, model, baseUrl, chunkChars: parsedChunk, topK: parsedTopK };
37
+ }
38
+ export function loadSemanticConfig(fs, aiwPath) {
39
+ const configPath = `${aiwPath}/${SEMANTIC_CONFIG_PATH}`;
40
+ if (!fs.exists(configPath))
41
+ return undefined;
42
+ return parseSemanticConfig(fs.read(configPath));
43
+ }
44
+ export function createOllamaEmbeddingProvider(config) {
45
+ const fetchImpl = (url, init) => fetch(url, init);
46
+ return {
47
+ async available() {
48
+ try {
49
+ const response = await fetchImpl(`${config.baseUrl}/api/tags`);
50
+ if (!response.ok)
51
+ return false;
52
+ const body = (await response.json());
53
+ const names = new Set((body.models ?? []).flatMap((entry) => typeof entry?.name === "string" && typeof entry?.model === "string"
54
+ ? [entry.name, entry.model]
55
+ : []));
56
+ return names.has(config.model);
57
+ }
58
+ catch {
59
+ return false;
60
+ }
61
+ },
62
+ async embed(texts) {
63
+ const vectors = [];
64
+ for (const text of texts) {
65
+ const response = await fetchImpl(`${config.baseUrl}/api/embeddings`, {
66
+ method: "POST",
67
+ headers: { "content-type": "application/json" },
68
+ body: JSON.stringify({ model: config.model, prompt: text }),
69
+ });
70
+ if (!response.ok)
71
+ throw new Error(`Embedding request failed for model ${config.model}`);
72
+ const body = (await response.json());
73
+ if (!Array.isArray(body.embedding) || body.embedding.some((v) => typeof v !== "number"))
74
+ throw new Error(`Embedding model ${config.model} returned an invalid vector`);
75
+ vectors.push(body.embedding);
76
+ }
77
+ return vectors;
78
+ },
79
+ };
80
+ }
81
+ export function chunkText(content, chunkChars) {
82
+ const lines = content.split("\n");
83
+ const chunks = [];
84
+ let current = [];
85
+ let length = 0;
86
+ for (const line of lines) {
87
+ if (length + line.length + 1 > chunkChars && current.length > 0) {
88
+ chunks.push(current.join("\n"));
89
+ current = [];
90
+ length = 0;
91
+ }
92
+ current.push(line);
93
+ length += line.length + 1;
94
+ }
95
+ if (current.length > 0 && current.join("").trim().length > 0)
96
+ chunks.push(current.join("\n"));
97
+ return chunks;
98
+ }
99
+ export function cosineSimilarity(a, b) {
100
+ let dot = 0;
101
+ let normA = 0;
102
+ let normB = 0;
103
+ for (let index = 0; index < a.length && index < b.length; index += 1) {
104
+ dot += a[index] * b[index];
105
+ normA += a[index] * a[index];
106
+ normB += b[index] * b[index];
107
+ }
108
+ if (normA === 0 || normB === 0)
109
+ return 0;
110
+ return dot / (Math.sqrt(normA) * Math.sqrt(normB));
111
+ }
112
+ export async function buildSemanticIndex(files, readContent, provider, config) {
113
+ const entries = [];
114
+ for (const file of files) {
115
+ const content = await readContent(file);
116
+ if (content === undefined)
117
+ continue;
118
+ for (const chunk of chunkText(content, config.chunkChars))
119
+ entries.push({ file, text: chunk });
120
+ }
121
+ if (entries.length === 0)
122
+ return { schema: 1, model: config.model, chunks: [] };
123
+ const vectors = await provider.embed(entries.map((entry) => entry.text));
124
+ return {
125
+ schema: 1,
126
+ model: config.model,
127
+ chunks: entries.map((entry, index) => ({ ...entry, vector: vectors[index] })),
128
+ };
129
+ }
130
+ export function serializeSemanticIndex(index) {
131
+ return JSON.stringify(index);
132
+ }
133
+ export function parseSemanticIndex(text) {
134
+ const parsed = JSON.parse(text);
135
+ if (parsed.schema !== 1 || typeof parsed.model !== "string" || !Array.isArray(parsed.chunks))
136
+ throw new Error("Invalid semantic index");
137
+ for (const chunk of parsed.chunks) {
138
+ if (typeof chunk.file !== "string" ||
139
+ typeof chunk.text !== "string" ||
140
+ !Array.isArray(chunk.vector) ||
141
+ chunk.vector.some((value) => typeof value !== "number"))
142
+ throw new Error("Invalid semantic index chunk");
143
+ }
144
+ return parsed;
145
+ }
146
+ export async function querySemanticIndex(index, query, provider, config) {
147
+ if (index.model !== config.model)
148
+ throw new Error(`Semantic index was built with ${index.model} but the active semantic model is ${config.model}`);
149
+ if (index.chunks.length === 0)
150
+ return [];
151
+ const [vector] = await provider.embed([query]);
152
+ return index.chunks
153
+ .map((chunk) => ({
154
+ file: chunk.file,
155
+ score: cosineSimilarity(vector, chunk.vector),
156
+ excerpt: chunk.text.split("\n").slice(0, 3).join("\n").slice(0, 200),
157
+ }))
158
+ .sort((a, b) => b.score - a.score)
159
+ .slice(0, config.topK);
160
+ }
package/dist/telemetry.js CHANGED
@@ -13,6 +13,7 @@ const TELEMETRY_COMMANDS = new Set([
13
13
  "context",
14
14
  "context-quality",
15
15
  "context-summary",
16
+ "decision",
16
17
  "doctor",
17
18
  "gate",
18
19
  "generate",
@@ -29,6 +30,7 @@ const TELEMETRY_COMMANDS = new Set([
29
30
  "rollback",
30
31
  "scan",
31
32
  "self-validate",
33
+ "semantic",
32
34
  "skills",
33
35
  "spec",
34
36
  "status",
package/dist/workflow.js CHANGED
@@ -21,6 +21,8 @@ import { validatePackageContract } from "./package-contract.js";
21
21
  import { parseLock, resolvePackage, serializeLock } from "./lockfile.js";
22
22
  import { executeVercelSkills, installVercelSkill, nodeCommandExecutor } from "./vercel-skills.js";
23
23
  import { scanProject } from "./scanner.js";
24
+ import { buildEvidenceTriageRequest, createOllamaDecisionProvider, loadDecisionConfig, DECISION_CONFIG_PATH, } from "./decision.js";
25
+ import { buildSemanticIndex, createOllamaEmbeddingProvider, loadSemanticConfig, parseSemanticIndex, querySemanticIndex, SEMANTIC_INDEX_PATH, serializeSemanticIndex, } from "./semantic.js";
24
26
  import { assertKnownPermissions, assertPermissions, assertPackagePermissions, serializePermissionReview, } from "./package-audit.js";
25
27
  import { planPackageUpdate } from "./package-updates.js";
26
28
  import { searchRegistry, serializeRegistry } from "./registry.js";
@@ -426,8 +428,126 @@ export async function runCommand(args, root, fs, services = {}) {
426
428
  .map((fact) => ` - key: ${fact.key}\n value: ${fact.value}\n state: ${fact.state}\n method: ${fact.method}\n confidence: ${fact.confidence}${fact.requiresConfirmation === undefined ? "" : `\n requires_confirmation: ${fact.requiresConfirmation}`}\n evidence: [${fact.evidence.map((item) => `"${item.source}"`).join(", ")}]`)
427
429
  .join("\n");
428
430
  fs.write(join(aiw, "profile.yml"), `schema: 1\nstatus: scanned\nfacts:\n${evidence}\nruntime:\n languages: [${profile.runtime.languages.join(", ")}]\nframeworks: [${profile.frameworks.join(", ")}]\npackage_manager: ${profile.packageManager}\nquality:\n linter: ${profile.quality.linter?.name ?? "unknown"}\n linter_command: ${profile.quality.linter?.command ?? "unknown"}\n formatter: ${profile.quality.formatter?.name ?? "unknown"}\n formatter_command: ${profile.quality.formatter?.command ?? "unknown"}\n typecheck: ${profile.quality.typecheck?.name ?? "unknown"}\n typecheck_command: ${profile.quality.typecheck?.command ?? "unknown"}\ntesting: ${profile.testing?.name ?? "unknown"}\ntesting_command: ${profile.testing?.command ?? "unknown"}\nci: [${profile.ci.join(", ")}]\nworkspaces: [${profile.workspaces.join(", ")}]\nproject_modules: ${JSON.stringify(profile.modules ?? [])}\npolicies:\n artifact_language: en\n`);
431
+ const semanticConfig = loadSemanticConfig(fs, aiw);
432
+ if (semanticConfig?.enabled) {
433
+ const provider = createOllamaEmbeddingProvider(semanticConfig);
434
+ if (!(await provider.available()))
435
+ return {
436
+ exitCode: 1,
437
+ error: `Semantic model ${semanticConfig.model} is not available at ${semanticConfig.baseUrl}.`,
438
+ };
439
+ const scanFiles = await (services.scanner?.scan(root) ?? scanProject(root));
440
+ const index = await buildSemanticIndex(scanFiles.files, async (file) => {
441
+ const absolute = join(root, file);
442
+ try {
443
+ return new TextDecoder("utf-8", { fatal: true }).decode(fs.readBytes?.(absolute) ?? Buffer.from(fs.read(absolute), "utf8"));
444
+ }
445
+ catch {
446
+ return undefined;
447
+ }
448
+ }, provider, semanticConfig);
449
+ fs.mkdir(join(aiw, "semantic"));
450
+ fs.write(join(aiw, SEMANTIC_INDEX_PATH), serializeSemanticIndex(index));
451
+ }
429
452
  return { exitCode: 0, output: "Project profile generated." };
430
453
  }
454
+ if (command === "decision") {
455
+ const usage = "Usage: aiw decision triage --artifact=<path> | aiw decision status";
456
+ const action = args[1];
457
+ const config = loadDecisionConfig(fs, aiw);
458
+ if (action === "status") {
459
+ if (!config)
460
+ return {
461
+ exitCode: 0,
462
+ output: `Decision model is not configured. Create .aiw/${DECISION_CONFIG_PATH} to enable it.`,
463
+ };
464
+ return {
465
+ exitCode: 0,
466
+ output: `Decision model ${config.enabled ? "enabled" : "disabled"}: ${config.model} at ${config.baseUrl}`,
467
+ };
468
+ }
469
+ if (action === "triage") {
470
+ const parsed = parseNamedOptions(args.slice(2), ["artifact"]);
471
+ if (!parsed?.artifact)
472
+ return { exitCode: 1, error: usage };
473
+ if (!config?.enabled)
474
+ return {
475
+ exitCode: 1,
476
+ error: "Decision model is disabled. Set decision.enabled=true in .aiw/decision.yml.",
477
+ };
478
+ const artifactPath = safeProjectFile(root, parsed.artifact, fs);
479
+ if (!fs.exists(artifactPath))
480
+ return { exitCode: 1, error: `Artifact not found: ${parsed.artifact}` };
481
+ const provider = createOllamaDecisionProvider(config);
482
+ if (!(await provider.available()))
483
+ return {
484
+ exitCode: 1,
485
+ error: `Decision model ${config.model} is not available at ${config.baseUrl}.`,
486
+ };
487
+ const decision = await provider.decide(buildEvidenceTriageRequest(fs.read(artifactPath)));
488
+ const gate = decision.answers.gate;
489
+ const evidence = decision.answers.evidence_complete;
490
+ return {
491
+ exitCode: 0,
492
+ output: [
493
+ "Advisory decision (never replaces human approval):",
494
+ ` gate: ${gate?.choice ?? "unknown"} (confidence ${formatConfidence(gate?.confidence)})`,
495
+ ` evidence_complete: ${formatNoul(evidence?.noul)}`,
496
+ ].join("\n"),
497
+ };
498
+ }
499
+ return { exitCode: 1, error: usage };
500
+ }
501
+ if (command === "semantic") {
502
+ const usage = "Usage: aiw semantic query --text=<query> | aiw semantic status";
503
+ const action = args[1];
504
+ if (action === "status") {
505
+ const config = loadSemanticConfig(fs, aiw);
506
+ if (!config)
507
+ return {
508
+ exitCode: 0,
509
+ output: `Semantic mapping is not configured. Create .aiw/${"semantic.yml"} to enable it.`,
510
+ };
511
+ const indexPath = join(aiw, SEMANTIC_INDEX_PATH);
512
+ const built = fs.exists(indexPath)
513
+ ? `indexed chunks: ${parseSemanticIndex(fs.read(indexPath)).chunks.length}`
514
+ : "index: not built (run aiw scan)";
515
+ return {
516
+ exitCode: 0,
517
+ output: `Semantic mapping ${config.enabled ? "enabled" : "disabled"}: model ${config.model} at ${config.baseUrl}\n${built}`,
518
+ };
519
+ }
520
+ if (action === "query") {
521
+ const parsed = parseNamedOptions(args.slice(2), ["text"]);
522
+ if (!parsed?.text)
523
+ return { exitCode: 1, error: usage };
524
+ const config = loadSemanticConfig(fs, aiw);
525
+ if (!config?.enabled)
526
+ return {
527
+ exitCode: 1,
528
+ error: "Semantic mapping is disabled. Set semantic.enabled=true in .aiw/semantic.yml.",
529
+ };
530
+ const indexPath = join(aiw, SEMANTIC_INDEX_PATH);
531
+ if (!fs.exists(indexPath))
532
+ return { exitCode: 1, error: "Semantic index is missing. Run `aiw scan` first." };
533
+ const provider = createOllamaEmbeddingProvider(config);
534
+ if (!(await provider.available()))
535
+ return {
536
+ exitCode: 1,
537
+ error: `Semantic model ${config.model} is not available at ${config.baseUrl}.`,
538
+ };
539
+ const matches = await querySemanticIndex(parseSemanticIndex(fs.read(indexPath)), parsed.text, provider, config);
540
+ return {
541
+ exitCode: 0,
542
+ output: matches.length === 0
543
+ ? "No semantic matches."
544
+ : matches
545
+ .map((match) => `- ${match.file} (score ${match.score.toFixed(3)})\n ${match.excerpt}`)
546
+ .join("\n"),
547
+ };
548
+ }
549
+ return { exitCode: 1, error: usage };
550
+ }
431
551
  if (command === "target") {
432
552
  const target = args[1];
433
553
  if (!target)
@@ -1586,7 +1706,7 @@ export async function runCommand(args, root, fs, services = {}) {
1586
1706
  }
1587
1707
  return {
1588
1708
  exitCode: 0,
1589
- output: "aiw install [--target target] | workflow <start|status|complete> [--id=id] | approve <stage> | reject <stage> --reason=text | skip <optional-stage> --reason=text | ui [--port=number] | orchestrate --plan=path [--execute] [--max-parallel=number] | telemetry <status|enable|disable> [privacy options] | plugin <create|validate> [options] | scan | self-validate --ticket=TYPE-000 | organization-policy --file=path [--replace] | policy-check --package=path [--package=path] | preset --file=path [--replace] | context | context-summary | context-quality | capabilities [--target=target] | token-usage [--stage=name --budget=number --used=number] | registry [--search=query] [--private=name] | registry configure --file=path [--replace] | skills <search|inspect|install|check|update> [options] | audit-package --package=path | verify-package --package=path --checksum=sha256 | brainstorm [--title=title] | spec [--title=title] | adr [--id=id --title=title] | plan [--title=title] | verify | trace | gate <stage> | recommend [--select=id,id] | status | doctor | repair | uninstall [--dry-run] | target <target> | rollback | confirm | resolve --package=path | update --package=path --target=target | validate [--package=path]",
1709
+ output: "aiw install [--target target] | workflow <start|status|complete> [--id=id] | approve <stage> | reject <stage> --reason=text | skip <optional-stage> --reason=text | ui [--port=number] | orchestrate --plan=path [--execute] [--max-parallel=number] | telemetry <status|enable|disable> [privacy options] | plugin <create|validate> [options] | scan | semantic <status|query> | decision <status|triage> | self-validate --ticket=TYPE-000 | organization-policy --file=path [--replace] | policy-check --package=path [--package=path] | preset --file=path [--replace] | context | context-summary | context-quality | capabilities [--target=target] | token-usage [--stage=name --budget=number --used=number] | registry [--search=query] [--private=name] | registry configure --file=path [--replace] | skills <search|inspect|install|check|update> [options] | audit-package --package=path | verify-package --package=path --checksum=sha256 | brainstorm [--title=title] | spec [--title=title] | adr [--id=id --title=title] | plan [--title=title] | verify | trace | gate <stage> | recommend [--select=id,id] | status | doctor | repair | uninstall [--dry-run] | target <target> | rollback | confirm | resolve --package=path | update --package=path --target=target | validate [--package=path]",
1590
1710
  };
1591
1711
  }
1592
1712
  catch (error) {
@@ -1661,6 +1781,12 @@ function approvalArtifactPath(aiw, stage) {
1661
1781
  };
1662
1782
  return resolve(aiw, paths[stage]);
1663
1783
  }
1784
+ function formatConfidence(confidence) {
1785
+ return confidence === undefined ? "unknown" : confidence.toFixed(3);
1786
+ }
1787
+ function formatNoul(noul) {
1788
+ return noul === undefined ? "unknown" : noul.toFixed(3);
1789
+ }
1664
1790
  function parseNamedOptions(args, allowed) {
1665
1791
  const entries = args.map((arg) => arg.match(/^--([a-z-]+)=(.+)$/)?.slice(1));
1666
1792
  if (args.some((arg) => arg.indexOf("=") !== arg.lastIndexOf("=")) ||
@@ -6,13 +6,13 @@ Make `/ai-init` a guided, evidence-backed onboarding flow. The initial `npx` ins
6
6
 
7
7
  ## User flow
8
8
 
9
- 1. **Model and consent.** Before repository analysis, recommend switching to a lower-cost model. Do not change model settings. Ask whether the user switched or explicitly wants to continue with the current model. End the agent turn after asking and wait for a reply before scanning, launching subagents, or calling tools. Silence, ambiguity, or a preselected widget value is not consent.
9
+ 1. **Model and consent.** Before repository analysis, recommend switching to a lower-cost model. Do not change model settings. Use the host's native blocking question widget, with no preselected value, to ask whether the user switched, wants to continue with the current model, or wants to stop. Do not scan or launch subagents until the widget has an explicit continue response. Silence, ambiguity, dismissal, or a default value is not consent.
10
10
  2. **Parallel project analysis.** Use the host's native subagent mechanism to start independent read-only scans in parallel. Assign separate work to (a) technologies and package/workspace topology, (b) architecture, modules, and code patterns, and (c) tests, quality tools, and CI. The initiating agent is the orchestrator: it consolidates findings, resolves conflicts using repository evidence, and records paths for every material conclusion. If the host cannot start subagents, do the same scans directly and say so.
11
11
  3. **Monorepo coverage.** Discover root and nested project manifests, workspace definitions, package boundaries, framework configs, tests, and module entry points. Analyze each workspace separately, then summarize shared tooling and cross-workspace relationships. Never infer that root dependencies apply to every workspace.
12
- 4. **Scan record.** Run the deterministic AI Workflow scan and combine it with the parallel analysis. Present technologies, architecture, patterns, modules, tests, quality commands, workspace topology, confidence, and source evidence. Do not include secret values. Ask for confirmation where conflicting or uncertain findings would affect installed guidance.
12
+ 4. **Scan record.** Run the deterministic AI Workflow scan and combine it with the parallel analysis. Present technologies, architecture, patterns, modules, tests, quality commands, workspace topology, confidence, and source evidence. Do not include secret values. Use the native blocking question widget to confirm, correct, or leave unresolved any conflicting or uncertain finding that would affect installed guidance.
13
13
  5. **Recommendations.** Match catalog capabilities to the consolidated project profile. Show concise tables grouped by resource category (skills, rules, agents, hooks, templates, and MCPs), with one row per item and a short, project-specific reason plus supporting evidence. Include resource types, required permissions, and project-specific configuration steps where relevant. Recommend an MCP only when repository evidence supports its use; disclose its publisher/source, capabilities, access, transport, and credential needs. Never invent credentials or silently enable an MCP.
14
14
  6. **Selection.** After the recommendation tables, use the coding host's native selection widget when it supports one. First offer: install all recommendations, customize, or install none. No option may be preselected or inferred. Wait for an explicit submission; a dismissed widget, blank response, silence, or ambiguity must stop progress without selecting or installing anything. For customization, show selectable blocks grouped by resource type, then the individual recommended items in each selected block; retain each item's concise reason and evidence in the selection details. Include MCPs as a separate block. Keep dependencies visible and select required dependencies together. If the host has no selection widget, ask an equivalent numbered prompt, end the turn, and wait for an explicit all/custom/none reply.
15
- 7. **Install and personalize.** After an explicit non-empty selection, show the exact resources and prerequisite capabilities that will be installed, ask for confirmation, end the turn, and wait. Install only after that confirmation arrives in a later turn. For “all”, install every recommendation that does not require an unapproved external permission. A missing external permission must not block independent bundled resources; report the external item as pending and give the user the exact retry command. For custom selection, install only selected items and their declared dependencies. Personalize resources only when project evidence materially changes their instructions (for example, test commands, workspace/module layout, framework patterns, or architectural boundaries). Preserve existing files and secrets; report conflicts rather than overwriting. MCP setup must use the selected host's supported project-level configuration, preserve existing configuration, use environment-variable references for secrets, and request separate approval for permissions or external execution.
15
+ 7. **Install and personalize.** After an explicit non-empty selection, show the exact resources and prerequisite capabilities that will be installed. Use the native blocking question widget with no default choice to approve the exact preview, request changes, or cancel. Do not sync while the widget is unanswered. For “all”, install every recommendation that does not require an unapproved external permission. A missing external permission must not block independent bundled resources; report the external item as pending and give the user the exact retry command. For custom selection, install only selected items and their declared dependencies. Personalize resources only when project evidence materially changes their instructions (for example, test commands, workspace/module layout, framework patterns, or architectural boundaries). Preserve existing files and secrets; report conflicts rather than overwriting. Use separate blocking question widgets to authorize MCP configuration, server execution, external access, and credentials; never combine these permissions.
16
16
  8. **Completion.** Report installed resources by block, project-specific changes, skipped recommendations, conflicts, MCP authentication/trust steps, and validation performed. Recommend restarting/reloading the coding-agent session so newly installed resources are discovered. Do not claim a restart is required when the host loads changes immediately; distinguish host behavior where known.
17
17
 
18
18
  ## Safety and compatibility
@@ -23,6 +23,7 @@ Make `/ai-init` a guided, evidence-backed onboarding flow. The initial `npx` ins
23
23
  - Selection and external permissions are separate decisions. Selecting an MCP is not permission to execute it or to expose credentials.
24
24
  - Resource writes use target adapters, ownership tracking, safe paths, atomic updates, and conflict reporting.
25
25
  - Host-specific capabilities (subagents, selection widgets, MCP configuration, restart semantics) are detected or described accurately; provide a safe manual fallback where unavailable.
26
+ - Every human decision uses the host's native blocking question widget when available, with no default choice; a prose prompt is permitted only as a documented fallback when the host has no blocking widget.
26
27
 
27
28
  ## Acceptance criteria
28
29
 
@@ -39,9 +40,10 @@ Make `/ai-init` a guided, evidence-backed onboarding flow. The initial `npx` ins
39
40
  - **AIINIT-010:** CLI tests cover mixed-stack monorepos, all/custom/none selection, permission gating, MCP config merging, personalization, conflicts, and completion output.
40
41
  - **AIINIT-011:** Custom selections can choose individual resource references by block, while `--select=all` selects every recommended capability and `--select=` selects none.
41
42
  - **AIINIT-013:** Sync previews exact additions, updates, preserved files, conflicts, and external permissions without writing; apply is bound to the approved plan fingerprint and is transactional. External skills and MCP actions require separate approvals, so network denial cannot partially apply local resources.
42
- - **AIINIT-014:** The bootstrap pauses the agent turn at the model choice, recommendation selection, and final install confirmation; no scan or install proceeds from silence, ambiguity, cancellation, or a default/preselected choice.
43
- - **AIINIT-015:** Every applicable SDD stage has a mandatory human approval checkpoint before the next stage; automated quality gates are explicitly distinct from human approval, and the agent must stop the turn until an explicit decision arrives.
43
+ - **AIINIT-014:** The bootstrap uses a blocking question widget for model choice, uncertain scan facts, recommendation selection, and final install confirmation; no scan or install proceeds from silence, ambiguity, cancellation, or a default/preselected choice.
44
+ - **AIINIT-015:** Every applicable SDD stage has a mandatory human approval checkpoint before the next stage; the agent uses the native blocking question widget with explicit approve/request-changes/reject choices, and automated quality gates remain distinct from human approval.
44
45
  - **AIINIT-016:** A versioned per-workflow approval ledger binds each human decision to the exact evidence fingerprint, blocks out-of-order commands, invalidates downstream approvals when evidence changes, and explains the next action in workflow status.
46
+ - **AIINIT-017:** Every human checkpoint uses the host's native blocking question widget when available, leaves choices unselected, and acts only after an explicit submission. If unavailable, the agent uses a numbered chat prompt and waits for a later explicit reply. The CLI must describe that it cannot independently prove which UI produced an approval.
45
47
 
46
48
  ## Delivery slices
47
49
 
@@ -47,7 +47,7 @@ Project analysis is hybrid: deterministic detectors establish observable facts,
47
47
 
48
48
  The SDD approval contract models the ordered stages brainstorming, specification, technical design, plan, implementation, verification, review, and traceability. Brainstorming and technical design are optional; skipping either requires a human-accepted reason. Every other stage must be approved. Automated quality-gate results are stored separately from human decisions and can only move an artifact to pending review, never directly to approved.
49
49
 
50
- Each approval is scoped to one workflow ID and bound to a SHA-256 fingerprint of the exact artifact reviewed, so a later feature cannot reuse earlier approvals. A content change or rejection makes that stage and its descendants stale. The CLI persists the versioned ledger in `.aiw/approvals.yml`, blocks stage commands when prerequisites are unresolved, and keeps passing quality gates distinct from human approval. Approval records provide an audit trail but cannot prove the identity of a person who responded in a host conversation.
50
+ Each approval is scoped to one workflow ID and bound to a SHA-256 fingerprint of the exact artifact reviewed, so a later feature cannot reuse earlier approvals. A content change or rejection makes that stage and its descendants stale. The CLI persists the versioned ledger in `.aiw/approvals.yml`, blocks stage commands when prerequisites are unresolved, and keeps passing quality gates distinct from human approval. The generated bootstrap and stage skills require the host's native blocking question widget for human decisions when available, with a waiting chat prompt as fallback. Approval records provide an audit trail but cannot prove the identity of a respondent or independently verify that the host widget was used.
51
51
 
52
52
  Every stage has a stable evidence path and an automated completeness gate. Design, implementation, review, and traceability use explicit report templates; a passing completeness check still leaves the stage pending until a human accepts the exact evidence.
53
53
 
package/docs/backlog.md CHANGED
@@ -68,6 +68,8 @@ Priority levels: **P0** blocks the product or makes it unsafe; **P1** is require
68
68
 
69
69
  ## Context and cost — P2
70
70
 
71
+ - [x] **SEM-001** Add flag-gated semantic mapping to scan and retrieval. _Acceptance:_ `.aiw/semantic.yml` with `enabled: true` verifies model availability before indexing, builds a versioned chunk index during `aiw scan`, and `aiw semantic query` returns ranked matches; missing or disabled configuration keeps scans fully deterministic and offline; semantic results are retrieval aids, never confirmed facts.
72
+ - [x] **SEM-002** Integrate a decision model (jev/clef-flash via Ollama `/v1/systemone`) for advisory gate triage. _Acceptance:_ `.aiw/decision.yml` with `enabled: true` verifies model availability before use; `aiw decision triage` returns advisory `evidence_complete` and `gate` decisions with confidence; disabled or missing configuration makes no network request; advisory output never replaces human approval. See `docs/decision-model-evaluation.md`.
71
73
  - [x] **CTX-001** Build layered project context store.
72
74
  - [x] **CTX-002** Add task-scoped context retrieval and delta loading.
73
75
  - [x] **CTX-003** Add summary cache and freshness invalidation.
@@ -0,0 +1,42 @@
1
+ # Decision Model Evaluation: jev/clef (clef-flash)
2
+
3
+ Status: implemented 2026-10-08 as an advisory triage layer behind a flag (`.aiw/decision.yml`, commands `aiw decision status` / `aiw decision triage`). It uses the `/v1/systemone` endpoint.
4
+
5
+ ## What the model offers
6
+
7
+ Local inspection (`ollama show clef-flash:latest`, Ollama 0.40.1, MLX runner):
8
+
9
+ | Property | Value |
10
+ | ------------------------- | -------------------------------------------- |
11
+ | Architecture | `clef` (purpose-built decision model by jev) |
12
+ | Parameters / quantization | 9.5B, mxfp8, ~12 GB |
13
+ | Context | 262,144 tokens |
14
+ | Capabilities | `decision`, `vision` |
15
+ | License | Apache 2.0 |
16
+
17
+ A dedicated decision model is architecturally attractive for AI Workflow: the SDD flow already separates automated quality gates from human approval, and an advisory triage layer between them could pre-screen evidence completeness, rank recommendations, and classify drift before a human reviews it.
18
+
19
+ ## API
20
+
21
+ Clef Flash answers typed questions over a supplied state in a single non-autoregressive forward pass (Ollama `/v1/systemone`). Question types used by AI Workflow:
22
+
23
+ - `noul` — yes/no with the probability that the answer is true (`evidence_complete`).
24
+ - `choice` — ordered options with per-option probabilities (`gate`: `ready` / `needs-work`).
25
+
26
+ ## Implemented integration
27
+
28
+ The earlier blocker (no exposed endpoint in Ollama 0.40.1) is resolved by the `/v1/systemone` endpoint documented at <https://ollama.com/library/clef-flash>. AI Workflow now ships the advisory layer:
29
+
30
+ - `src/decision.ts`: `DecisionConfig` (`.aiw/decision.yml`, flag-gated: `enabled` / `model` / `baseUrl`), a `DecisionProvider` with availability verification (`/api/tags`), typed question/response contracts, and `buildEvidenceTriageRequest` for pre-gate triage.
31
+ - `aiw decision status` and `aiw decision triage --artifact=<path>`: advisory `evidence_complete` (noul) and `gate` (`ready` / `needs-work`) decisions over any project artifact, with explicit confidence reporting.
32
+
33
+ Observed behavior on real plans: an evidence-free plan scored `gate: ready` with confidence 0.044 and `evidence_complete` 0.260, while a fully evidenced plan scored confidence 0.718 and `evidence_complete` 0.924 — low confidence signals an inconclusive decision rather than approval.
34
+
35
+ ## Future integration points (advisory)
36
+
37
+ - **Recommendation ranking**: order capability recommendations by decision-model relevance to the confirmed profile.
38
+ - **Semantic re-ranking**: combine embedding recall (`aiw semantic query`) with clef re-ranking for sharper context selection during interpretation.
39
+
40
+ ## Hard boundary (unchanged)
41
+
42
+ Decision-model output is `inferred` evidence with confidence, never `confirmed`. It can move an artifact to _pending review_, never to _approved_, and cannot replace the fingerprinted human approval ledger. This matches the existing approval contract and keeps the audit trail human-owned.
@@ -0,0 +1,39 @@
1
+ # Semantic Mapping
2
+
3
+ AI Workflow can build a semantic index of the scanned project with a local embedding model such as `embeddinggemma-2` served by Ollama. Semantic mapping is opt-in and flag-gated: it activates only when a valid configuration exists, and every scan and flow query re-reads the flag.
4
+
5
+ ## Configuration
6
+
7
+ Create `.aiw/semantic.yml`:
8
+
9
+ ```yaml
10
+ semantic:
11
+ enabled: true
12
+ model: embeddinggemma-2:latest
13
+ baseUrl: http://localhost:11434
14
+ chunkChars: 1200 # optional, minimum 200
15
+ topK: 8 # optional, results per query
16
+ ```
17
+
18
+ - `enabled: false` (or a missing file) disables semantic mapping entirely; scans stay deterministic and make no network request.
19
+ - When enabled, `aiw scan` verifies that `model` is present at `baseUrl` (`GET /api/tags`) before indexing. A missing model fails the scan with an actionable error instead of producing a partial index.
20
+
21
+ ## Indexing during scan
22
+
23
+ When semantic mapping is enabled, `aiw scan` additionally:
24
+
25
+ 1. Reads the scanned files with strict UTF-8 decoding (unreadable files are skipped).
26
+ 2. Splits each file into line-aligned chunks of at most `chunkChars` characters.
27
+ 3. Embeds each chunk with `POST /api/embeddings`.
28
+ 4. Writes the versioned index to `.aiw/semantic/index.json` (`schema: 1`).
29
+
30
+ The index stores the model name; queries against an index built with a different model fail explicitly.
31
+
32
+ ## Queries during the flow
33
+
34
+ `aiw semantic query --text="<natural language>"` ranks indexed chunks by cosine similarity and returns the top `topK` matches with file, score, and excerpt. `aiw semantic status` reports whether mapping is configured and whether the index is built.
35
+
36
+ ## Boundaries
37
+
38
+ - Semantic results are retrieval aids, not evidence. They never create confirmed project facts and never bypass human approval gates.
39
+ - The index is derived state; delete and rebuild it with `aiw scan` after structural changes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multileaf/ai-workflow",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Portable spec-driven development workflow for AI coding agents",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -5,6 +5,8 @@ description: Turn an early product idea into structured goals, assumptions, cons
5
5
 
6
6
  # Brainstorming
7
7
 
8
+ For every approval or optional skip, use the host's native blocking question widget, not a prose-only prompt when the widget is available. Leave choices unselected and offer “Approve and continue”, “Request changes”, and “Reject/stop”; for a skip, offer “Accept skip” or “Do not skip”. Wait for an actual submission before running `aiw approve`, `aiw reject`, or `aiw skip` or proceeding. Collect revision/skip reasons in a follow-up free-text widget. If the host has no blocking widget, use a numbered chat prompt and end the turn until an explicit reply arrives. The CLI records the decision but cannot verify that a widget was used.
9
+
8
10
  Separate facts, hypotheses, decisions, and open questions. Identify users, desired outcomes, constraints, risks, and non-goals. Do not prematurely prescribe implementation. Produce a versioned brainstorm artifact in English.
9
11
 
10
12
  Start an AI Workflow session for each feature with `npx --yes --package=@multileaf/ai-workflow -- aiw workflow start` unless one is already active. When the request is already clear, explain why brainstorming can be skipped, ask the human to accept the skip, stop, and record only an explicit acceptance with `aiw skip brainstorming --reason=<accepted reason>`. Otherwise create `.aiw/generated/specs/brainstorm.md`, run `aiw gate brainstorming`, present the exact artifact and a concise summary, and ask whether to proceed to requirements. Stop the turn and wait. Only after an explicit approval, record it with `aiw approve brainstorming`; do not start or scaffold requirements before that command succeeds. If changes are requested, first record `aiw reject brainstorming --reason=<feedback>`, then revise the artifact, rerun its gate, and ask again. Use `npx --yes --package=@multileaf/ai-workflow -- aiw <command>` when no global CLI is available.
@@ -5,6 +5,8 @@ description: Review code for correctness, maintainability, design quality, and r
5
5
 
6
6
  # Code Review
7
7
 
8
+ For every approval, rejection, or optional skip, use the host's native blocking question widget, not a prose-only prompt when the widget is available. Leave choices unselected and offer “Approve and continue”, “Request changes”, and “Reject/stop”; for a skip, offer “Accept skip” or “Do not skip”. Wait for an actual submission before running `aiw approve`, `aiw reject`, or `aiw skip` or proceeding. Collect revision/skip reasons in a follow-up free-text widget. If the host has no blocking widget, use a numbered chat prompt and end the turn until an explicit reply arrives. The CLI records the decision but cannot verify that a widget was used.
9
+
8
10
  Review behavior, boundaries, cohesion, naming, types, error handling, duplication, complexity, tests, and documentation. Prioritize actionable findings with severity and evidence.
9
11
 
10
12
  Write `.aiw/generated/reports/code-review.md` with the scope, findings (including an explicit `None` when clear), checks, residual risks, and decision. Run `aiw gate review`, present the exact report, and ask whether to address findings or accept the residual risk. Stop and wait for an explicit decision. Only after approval, record it with `aiw approve review`; do not start traceability before it succeeds. If changes are requested, first record `aiw reject review --reason=<feedback>`, return to implementation, and require verification and review again. Review completeness is an automated gate; it does not replace human approval. Invoke commands through `npx --yes --package=@multileaf/ai-workflow -- aiw <command>` when no global binary exists.
@@ -5,6 +5,8 @@ description: Break an approved design into independently verifiable implementati
5
5
 
6
6
  # Implementation Planning
7
7
 
8
+ For every approval, rejection, or optional skip, use the host's native blocking question widget, not a prose-only prompt when the widget is available. Leave choices unselected and offer “Approve and continue”, “Request changes”, and “Reject/stop”; for a skip, offer “Accept skip” or “Do not skip”. Wait for an actual submission before running `aiw approve`, `aiw reject`, or `aiw skip` or proceeding. Collect revision/skip reasons in a follow-up free-text widget. If the host has no blocking widget, use a numbered chat prompt and end the turn until an explicit reply arrives. The CLI records the decision but cannot verify that a widget was used.
9
+
8
10
  Create small ordered tasks. Each task must reference requirements, affected modules, tests, commands, dependencies, and completion evidence.
9
11
 
10
12
  Write `.aiw/generated/plans/implementation-plan.md` with ordered tasks, requirement links, affected modules, tests, validation, dependencies, risks, and completion evidence. After `aiw gate plan` passes, present the exact plan to the human and ask for explicit approval. Stop and wait; a gate pass is not approval. Do not edit implementation files until approval arrives and `aiw approve plan` succeeds. If changes are requested, first record `aiw reject plan --reason=<feedback>`, then revise the plan, rerun its gate, and ask again. After approval, implement in task order. Use `tdd-development` when installed and the project has a test workflow; otherwise use existing validation. When implementation is complete, write `.aiw/generated/reports/implementation-report.md` with changed files, tests, validation results, and deviations; run `aiw gate implementation`, present the diff and evidence to the human, stop, and wait for explicit approval. Record approval with `aiw approve implementation` before verification. Invoke CLI commands through `npx --yes --package=@multileaf/ai-workflow -- aiw <command>` when no global binary exists.
@@ -5,4 +5,6 @@ description: Inspect a repository, build a project profile, and prepare AI Workf
5
5
 
6
6
  # Project Initialization
7
7
 
8
+ Use the host's native blocking question widget for uncertain project facts and setup decisions when available. Leave choices unselected; offer to confirm the evidence, correct it, or leave it unresolved. Do not make an uncertain inference project policy until the widget is explicitly submitted. If the host has no blocking widget, ask one numbered question in chat and wait for an explicit response before proceeding.
9
+
8
10
  Inspect structure and configuration safely. Detect languages, frameworks, package managers, scripts, tests, quality tools, CI, and conventions. Record evidence and confidence in `.aiw/profile.yml`. Ask for confirmation before treating uncertain inferences as facts. Recommend capabilities with rationale, permissions, and conflicts.
@@ -5,6 +5,8 @@ description: Convert a product idea into testable, traceable requirements.
5
5
 
6
6
  # Requirements Specification
7
7
 
8
+ For every approval, rejection, or optional skip, use the host's native blocking question widget, not a prose-only prompt when the widget is available. Leave choices unselected and offer “Approve and continue”, “Request changes”, and “Reject/stop”; for a skip, offer “Accept skip” or “Do not skip”. Wait for an actual submission before running `aiw approve`, `aiw reject`, or `aiw skip` or proceeding. Collect revision/skip reasons in a follow-up free-text widget. If the host has no blocking widget, use a numbered chat prompt and end the turn until an explicit reply arrives. The CLI records the decision but cannot verify that a widget was used.
9
+
8
10
  Give each requirement a stable identifier. Define observable behavior, acceptance criteria, constraints, and unresolved questions. Link requirements to their source brainstorm and distinguish confirmed facts from assumptions. Do not approve ambiguous critical requirements.
9
11
 
10
12
  Create `.aiw/generated/specs/specification.md` and run `aiw gate specification`. Present the exact requirements and unresolved questions to the human and ask for explicit approval. Stop the turn and wait; a passing gate is not approval. Only after approval, run `aiw approve specification`; do not create design or plan artifacts before it succeeds. If changes are requested, first record `aiw reject specification --reason=<feedback>`, then revise the specification, rerun its gate, and ask again. After approval, use `technical-design` when the change affects architecture or has consequential design choices. Otherwise explain why design can be skipped, ask the human to accept, stop, and record an explicit acceptance with `aiw skip technical-design --reason=<accepted reason>` before planning. Invoke commands through `npx --yes --package=@multileaf/ai-workflow -- aiw <command>` if `aiw` is not installed globally.
@@ -5,6 +5,8 @@ description: Implement repository changes through test-first development.
5
5
 
6
6
  # TDD Development
7
7
 
8
+ For implementation approval or rejection, use the host's native blocking question widget, not a prose-only prompt when the widget is available. Leave choices unselected and offer “Approve and continue”, “Request changes”, and “Reject/stop”. Wait for an actual submission before running `aiw approve` or `aiw reject` or starting verification. Collect requested changes in a follow-up free-text widget. If the host has no blocking widget, use a numbered chat prompt and end the turn until an explicit reply arrives. The CLI records the decision but cannot verify that a widget was used.
9
+
8
10
  Follow Red, Green, Refactor, Verify. Write a failing behavioral test first, implement the smallest change, refactor for clarity and design quality, then run the relevant quality gate. Never weaken a test to make an implementation pass.
9
11
 
10
12
  After implementation tasks are green and refactored, write `.aiw/generated/reports/implementation-report.md` with changed files, tests, validation results, and deviations. Run `aiw gate implementation`, present the diff, changed requirements, test commands, and exact evidence to the human. Ask for explicit approval to proceed to verification, end the turn, and wait. Do not invoke verification until the human approves and `aiw approve implementation` succeeds. If changes are requested, first record `aiw reject implementation --reason=<feedback>`, remain in implementation, repeat relevant tests, update the report, rerun its gate, and ask again. Invoke CLI commands through `npx --yes --package=@multileaf/ai-workflow -- aiw <command>` when no global binary exists.
@@ -5,6 +5,8 @@ description: Design modular technical solutions from approved requirements.
5
5
 
6
6
  # Technical Design
7
7
 
8
+ For every approval, rejection, or optional skip, use the host's native blocking question widget, not a prose-only prompt when the widget is available. Leave choices unselected and offer “Approve and continue”, “Request changes”, and “Reject/stop”; for a skip, offer “Accept skip” or “Do not skip”. Wait for an actual submission before running `aiw approve`, `aiw reject`, or `aiw skip` or proceeding. Collect revision/skip reasons in a follow-up free-text widget. If the host has no blocking widget, use a numbered chat prompt and end the turn until an explicit reply arrives. The CLI records the decision but cannot verify that a widget was used.
9
+
8
10
  Map requirements to components, interfaces, data flows, and validation strategy. Compare meaningful alternatives and record decisions with rationale, risks, and consequences. Prefer small cohesive modules, explicit contracts, dependency inversion, and reversible changes.
9
11
 
10
12
  Write the reviewable design to `.aiw/generated/reports/technical-design.md` using the technical-design template. Link each design and ADR to affected requirements. Run `aiw gate technical-design`, present the exact design and decisions to the human, and ask for explicit approval. Stop and wait; do not create an implementation plan until approval arrives. Only after approval, record it with `aiw approve technical-design`. If changes are requested, first record `aiw reject technical-design --reason=<feedback>`, then revise the design/ADRs, rerun the gate, and ask again. If no technical design is needed, obtain explicit human acceptance and record `aiw skip technical-design --reason=<accepted reason>`. Use `npx --yes --package=@multileaf/ai-workflow -- aiw <command>` when no global CLI is available.
@@ -5,6 +5,8 @@ description: Verify implementation against requirements and produce traceable ev
5
5
 
6
6
  # Verification
7
7
 
8
+ For every approval or rejection, use the host's native blocking question widget, not a prose-only prompt when the widget is available. Leave choices unselected and offer “Approve and continue”, “Request changes”, and “Reject/stop”. Wait for an actual submission before running `aiw approve` or `aiw reject` or proceeding to the next stage. Collect requested changes in a follow-up free-text widget. If the host has no blocking widget, use a numbered chat prompt and end the turn until an explicit reply arrives. The CLI records the decision but cannot verify that a widget was used.
9
+
8
10
  Check every acceptance criterion, quality gate, security requirement, and affected adapter. Report passed checks, missing evidence, failures, and residual risks. A task is not complete when required evidence is missing.
9
11
 
10
12
  Write `.aiw/generated/reports/verification-report.md` using the verification-report template and validate it with `npx --yes --package=@multileaf/ai-workflow -- aiw gate verification`. Present the exact report, checks, missing evidence, and residual risks to the human. Ask for explicit approval to proceed, end the turn, and wait; an automated gate pass is not human acceptance. Only after approval, record it with `aiw approve verification`. Do not start code review or final traceability before that command succeeds. If verification fails or changes are requested, record `aiw reject verification --reason=<feedback>` before editing the report or returning to implementation, then repeat verification, update the report, rerun the gate, and ask again. Use npx package invocation for other `aiw` commands when no global binary exists.