@multileaf/ai-workflow 0.3.0 → 0.4.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 +12 -0
- package/dist/cli.js +1 -1
- package/dist/decision.d.ts +2 -0
- package/dist/decision.js +21 -9
- package/dist/semantic.d.ts +3 -1
- package/dist/semantic.js +26 -11
- package/dist/settings.d.ts +11 -0
- package/dist/settings.js +70 -0
- package/dist/telemetry.d.ts +4 -1
- package/dist/telemetry.js +44 -3
- package/dist/workflow.d.ts +2 -0
- package/dist/workflow.js +60 -16
- package/docs/semantic-mapping.md +2 -2
- package/docs/telemetry.md +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@ All notable changes to this project are documented in this file. The format foll
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.4.0] - 2026-10-08
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Unified user-local settings into a single `.aiw/settings.yml` with `semantic`, `decision`, `telemetry`, and `overrides` sections. The file is local user state, not committed project state. Interactive onboarding and the `telemetry`/`confirm` commands write to it, and legacy `.aiw/semantic.yml`, `.aiw/decision.yml`, `.aiw/telemetry.yml`, and `.aiw/overrides.yml` files are still read for backward compatibility. `uninstall` removes `settings.yml` and all four legacy files.
|
|
12
|
+
|
|
13
|
+
## [0.3.1] - 2026-10-08
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Interactive optional-model onboarding during `aiw install`: in an interactive terminal, the installer asks whether to enable semantic mapping and advisory decision triage and writes `.aiw/semantic.yml` / `.aiw/decision.yml` with defaults before `/ai-init` runs. Non-interactive installs skip the prompts, and existing configuration files are never overwritten.
|
|
18
|
+
|
|
7
19
|
## [0.3.0] - 2026-10-08
|
|
8
20
|
|
|
9
21
|
### Added
|
package/dist/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ async function runWithTelemetry(args, root, services) {
|
|
|
17
17
|
const result = await runCommand(args, root, nodeFileSystem, dependencies);
|
|
18
18
|
if (args[0] === "ui" || (args[0] === "telemetry" && result.exitCode !== 0))
|
|
19
19
|
return result;
|
|
20
|
-
await recordConfiguredTelemetry(nodeFileSystem, join(root, ".aiw
|
|
20
|
+
await recordConfiguredTelemetry(nodeFileSystem, join(root, ".aiw"), services.telemetry, args[0], result);
|
|
21
21
|
return result;
|
|
22
22
|
}
|
|
23
23
|
if (process.argv[1]?.endsWith("cli.js") || process.argv[1]?.endsWith("/aiw")) {
|
package/dist/decision.d.ts
CHANGED
|
@@ -42,6 +42,8 @@ export type DecisionProvider = {
|
|
|
42
42
|
};
|
|
43
43
|
export declare const DECISION_CONFIG_PATH = "decision.yml";
|
|
44
44
|
export declare const DEFAULT_DECISION_CONFIG: DecisionConfig;
|
|
45
|
+
export declare function parseDecisionMapping(raw: unknown): DecisionConfig;
|
|
46
|
+
export declare function serializeDecisionConfig(config: DecisionConfig): string;
|
|
45
47
|
export declare function parseDecisionConfig(text: string): DecisionConfig;
|
|
46
48
|
export declare function loadDecisionConfig(fs: FileSystem, aiwPath: string): DecisionConfig | undefined;
|
|
47
49
|
export declare function parseDecisionResponse(text: string): DecisionResponse;
|
package/dist/decision.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { parseDocument } from "yaml";
|
|
2
|
+
import { loadSettings } from "./settings.js";
|
|
2
3
|
export const DECISION_CONFIG_PATH = "decision.yml";
|
|
3
4
|
export const DEFAULT_DECISION_CONFIG = {
|
|
4
5
|
enabled: false,
|
|
5
6
|
model: "clef-flash:latest",
|
|
6
7
|
baseUrl: "http://localhost:11434",
|
|
7
8
|
};
|
|
8
|
-
export function
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const model = raw.model;
|
|
16
|
-
const baseUrl = raw.baseUrl;
|
|
9
|
+
export function parseDecisionMapping(raw) {
|
|
10
|
+
if (!raw || typeof raw !== "object")
|
|
11
|
+
throw new Error("decision must be a YAML mapping");
|
|
12
|
+
const record = raw;
|
|
13
|
+
const enabled = record.enabled;
|
|
14
|
+
const model = record.model;
|
|
15
|
+
const baseUrl = record.baseUrl;
|
|
17
16
|
if (typeof enabled !== "boolean")
|
|
18
17
|
throw new Error("decision.enabled must be a boolean");
|
|
19
18
|
if (!enabled)
|
|
@@ -24,7 +23,20 @@ export function parseDecisionConfig(text) {
|
|
|
24
23
|
throw new Error("decision.baseUrl must be an http(s) URL when decision mapping is enabled");
|
|
25
24
|
return { enabled: true, model, baseUrl };
|
|
26
25
|
}
|
|
26
|
+
export function serializeDecisionConfig(config) {
|
|
27
|
+
return `decision:\n enabled: ${config.enabled}\n model: ${config.model}\n baseUrl: ${config.baseUrl}\n`;
|
|
28
|
+
}
|
|
29
|
+
export function parseDecisionConfig(text) {
|
|
30
|
+
const document = parseDocument(text);
|
|
31
|
+
const node = document.get("decision");
|
|
32
|
+
if (document.errors.length > 0 || !node || typeof node !== "object")
|
|
33
|
+
throw new Error("Decision configuration must be a YAML object with a `decision` mapping");
|
|
34
|
+
return parseDecisionMapping(node.toJSON());
|
|
35
|
+
}
|
|
27
36
|
export function loadDecisionConfig(fs, aiwPath) {
|
|
37
|
+
const settings = loadSettings(fs, aiwPath);
|
|
38
|
+
if (settings !== undefined)
|
|
39
|
+
return "decision" in settings ? parseDecisionMapping(settings.decision) : undefined;
|
|
28
40
|
const configPath = `${aiwPath}/${DECISION_CONFIG_PATH}`;
|
|
29
41
|
if (!fs.exists(configPath))
|
|
30
42
|
return undefined;
|
package/dist/semantic.d.ts
CHANGED
|
@@ -28,8 +28,10 @@ export type EmbeddingProvider = {
|
|
|
28
28
|
export declare const SEMANTIC_CONFIG_PATH = "semantic.yml";
|
|
29
29
|
export declare const SEMANTIC_INDEX_PATH = "semantic/index.json";
|
|
30
30
|
export declare const DEFAULT_SEMANTIC_CONFIG: SemanticConfig;
|
|
31
|
-
export declare function
|
|
31
|
+
export declare function parseSemanticMapping(raw: unknown): SemanticConfig;
|
|
32
|
+
export declare function serializeSemanticConfig(config: SemanticConfig): string;
|
|
32
33
|
export declare function loadSemanticConfig(fs: FileSystem, aiwPath: string): SemanticConfig | undefined;
|
|
34
|
+
export declare function parseSemanticConfig(text: string): SemanticConfig;
|
|
33
35
|
export declare function createOllamaEmbeddingProvider(config: SemanticConfig): EmbeddingProvider;
|
|
34
36
|
export declare function chunkText(content: string, chunkChars: number): string[];
|
|
35
37
|
export declare function cosineSimilarity(a: number[], b: number[]): number;
|
package/dist/semantic.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { parseDocument } from "yaml";
|
|
2
|
+
import { loadSettings } from "./settings.js";
|
|
2
3
|
export const SEMANTIC_CONFIG_PATH = "semantic.yml";
|
|
3
4
|
export const SEMANTIC_INDEX_PATH = "semantic/index.json";
|
|
4
5
|
export const DEFAULT_SEMANTIC_CONFIG = {
|
|
@@ -8,17 +9,15 @@ export const DEFAULT_SEMANTIC_CONFIG = {
|
|
|
8
9
|
chunkChars: 1200,
|
|
9
10
|
topK: 8,
|
|
10
11
|
};
|
|
11
|
-
export function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const chunkChars = raw.chunkChars;
|
|
21
|
-
const topK = raw.topK;
|
|
12
|
+
export function parseSemanticMapping(raw) {
|
|
13
|
+
if (!raw || typeof raw !== "object")
|
|
14
|
+
throw new Error("semantic must be a YAML mapping");
|
|
15
|
+
const raw2 = raw;
|
|
16
|
+
const enabled = raw2.enabled;
|
|
17
|
+
const model = raw2.model;
|
|
18
|
+
const baseUrl = raw2.baseUrl;
|
|
19
|
+
const chunkChars = raw2.chunkChars;
|
|
20
|
+
const topK = raw2.topK;
|
|
22
21
|
if (typeof enabled !== "boolean")
|
|
23
22
|
throw new Error("semantic.enabled must be a boolean");
|
|
24
23
|
if (!enabled)
|
|
@@ -35,12 +34,28 @@ export function parseSemanticConfig(text) {
|
|
|
35
34
|
throw new Error("semantic.topK must be a positive integer");
|
|
36
35
|
return { enabled: true, model, baseUrl, chunkChars: parsedChunk, topK: parsedTopK };
|
|
37
36
|
}
|
|
37
|
+
export function serializeSemanticConfig(config) {
|
|
38
|
+
return `semantic:\n enabled: ${config.enabled}\n model: ${config.model}\n baseUrl: ${config.baseUrl}\n chunkChars: ${config.chunkChars}\n topK: ${config.topK}\n`;
|
|
39
|
+
}
|
|
38
40
|
export function loadSemanticConfig(fs, aiwPath) {
|
|
41
|
+
const settings = loadSettings(fs, aiwPath);
|
|
42
|
+
if (settings !== undefined)
|
|
43
|
+
return "semantic" in settings ? parseSemanticMapping(settings.semantic) : undefined;
|
|
39
44
|
const configPath = `${aiwPath}/${SEMANTIC_CONFIG_PATH}`;
|
|
40
45
|
if (!fs.exists(configPath))
|
|
41
46
|
return undefined;
|
|
42
47
|
return parseSemanticConfig(fs.read(configPath));
|
|
43
48
|
}
|
|
49
|
+
function parseSemanticDocument(text) {
|
|
50
|
+
const document = parseDocument(text);
|
|
51
|
+
const node = document.get("semantic");
|
|
52
|
+
if (document.errors.length > 0 || !node || typeof node !== "object")
|
|
53
|
+
throw new Error("Semantic configuration must be a YAML object with a `semantic` mapping");
|
|
54
|
+
return parseSemanticMapping(node.toJSON());
|
|
55
|
+
}
|
|
56
|
+
export function parseSemanticConfig(text) {
|
|
57
|
+
return parseSemanticDocument(text);
|
|
58
|
+
}
|
|
44
59
|
export function createOllamaEmbeddingProvider(config) {
|
|
45
60
|
const fetchImpl = (url, init) => fetch(url, init);
|
|
46
61
|
return {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FileSystem } from "./types.js";
|
|
2
|
+
import type { FactOverride } from "./confirmation.js";
|
|
3
|
+
export declare const SETTINGS_PATH = "settings.yml";
|
|
4
|
+
export declare const LEGACY_SETTINGS_PATHS: readonly ["semantic.yml", "decision.yml", "telemetry.yml", "overrides.yml"];
|
|
5
|
+
export type SettingsDocument = Record<string, unknown>;
|
|
6
|
+
export declare function loadSettings(fs: FileSystem, aiwPath: string): SettingsDocument | undefined;
|
|
7
|
+
export declare function saveSettingsSection(fs: FileSystem, aiwPath: string, section: string, value: unknown): void;
|
|
8
|
+
export declare function serializeSettingOverrides(overrides: FactOverride[]): unknown;
|
|
9
|
+
export declare function readFactOverrides(fs: FileSystem, aiwPath: string): FactOverride[];
|
|
10
|
+
export declare function saveSettingOverrides(fs: FileSystem, aiwPath: string, overrides: FactOverride[]): void;
|
|
11
|
+
export declare function parseSettingOverrides(raw: unknown): FactOverride[];
|
package/dist/settings.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { parseDocument, stringify } from "yaml";
|
|
2
|
+
import { parseOverrides } from "./confirmation.js";
|
|
3
|
+
export const SETTINGS_PATH = "settings.yml";
|
|
4
|
+
export const LEGACY_SETTINGS_PATHS = [
|
|
5
|
+
"semantic.yml",
|
|
6
|
+
"decision.yml",
|
|
7
|
+
"telemetry.yml",
|
|
8
|
+
"overrides.yml",
|
|
9
|
+
];
|
|
10
|
+
export function loadSettings(fs, aiwPath) {
|
|
11
|
+
const path = `${aiwPath}/${SETTINGS_PATH}`;
|
|
12
|
+
if (!fs.exists(path))
|
|
13
|
+
return undefined;
|
|
14
|
+
const document = parseDocument(fs.read(path));
|
|
15
|
+
if (document.errors.length > 0)
|
|
16
|
+
throw new Error(`Settings file ${path} is not valid YAML`);
|
|
17
|
+
const root = document.toJSON();
|
|
18
|
+
if (root === null)
|
|
19
|
+
return {};
|
|
20
|
+
if (typeof root !== "object" || Array.isArray(root))
|
|
21
|
+
throw new Error(`Settings file ${path} must contain a YAML mapping`);
|
|
22
|
+
return root;
|
|
23
|
+
}
|
|
24
|
+
export function saveSettingsSection(fs, aiwPath, section, value) {
|
|
25
|
+
const path = `${aiwPath}/${SETTINGS_PATH}`;
|
|
26
|
+
const existing = loadSettings(fs, aiwPath) ?? {};
|
|
27
|
+
existing[section] = value;
|
|
28
|
+
const document = parseDocument(stringify(existing));
|
|
29
|
+
document.set("schema", 1);
|
|
30
|
+
fs.write(path, document.toString());
|
|
31
|
+
}
|
|
32
|
+
export function serializeSettingOverrides(overrides) {
|
|
33
|
+
return overrides.map((item) => ({
|
|
34
|
+
key: item.key,
|
|
35
|
+
action: item.action,
|
|
36
|
+
...(item.value ? { value: item.value } : {}),
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
export function readFactOverrides(fs, aiwPath) {
|
|
40
|
+
const settings = loadSettings(fs, aiwPath);
|
|
41
|
+
if (settings !== undefined)
|
|
42
|
+
return "overrides" in settings ? parseSettingOverrides(settings.overrides) : [];
|
|
43
|
+
const legacyPath = `${aiwPath}/overrides.yml`;
|
|
44
|
+
if (!fs.exists(legacyPath))
|
|
45
|
+
return [];
|
|
46
|
+
return parseOverrides(fs.read(legacyPath));
|
|
47
|
+
}
|
|
48
|
+
export function saveSettingOverrides(fs, aiwPath, overrides) {
|
|
49
|
+
saveSettingsSection(fs, aiwPath, "overrides", serializeSettingOverrides(overrides));
|
|
50
|
+
}
|
|
51
|
+
export function parseSettingOverrides(raw) {
|
|
52
|
+
if (!Array.isArray(raw))
|
|
53
|
+
throw new Error("Settings overrides section must be a list");
|
|
54
|
+
return raw.map((item) => {
|
|
55
|
+
if (!item || typeof item !== "object")
|
|
56
|
+
throw new Error("Invalid settings override entry");
|
|
57
|
+
const entry = item;
|
|
58
|
+
if (typeof entry.key !== "string" || entry.key.trim() === "")
|
|
59
|
+
throw new Error("Settings override key must be a non-empty string");
|
|
60
|
+
if (entry.action !== "accept" && entry.action !== "reject" && entry.action !== "edit")
|
|
61
|
+
throw new Error(`Invalid settings override action for key ${entry.key}`);
|
|
62
|
+
if (entry.action === "edit" && (typeof entry.value !== "string" || entry.value === ""))
|
|
63
|
+
throw new Error(`Settings override ${entry.key} requires a value for an edit action`);
|
|
64
|
+
return {
|
|
65
|
+
key: entry.key,
|
|
66
|
+
action: entry.action,
|
|
67
|
+
...(typeof entry.value === "string" && entry.value !== "" ? { value: entry.value } : {}),
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
}
|
package/dist/telemetry.d.ts
CHANGED
|
@@ -14,7 +14,10 @@ export interface TelemetryClient {
|
|
|
14
14
|
record(event: TelemetryEvent): Promise<void>;
|
|
15
15
|
}
|
|
16
16
|
export declare const DEFAULT_TELEMETRY_CONFIG: TelemetryConfig;
|
|
17
|
+
export declare function parseTelemetryMapping(raw: unknown): TelemetryConfig;
|
|
18
|
+
export declare function serializeTelemetryMapping(config: TelemetryConfig): unknown;
|
|
19
|
+
export declare function loadTelemetryConfig(fs: FileSystem, aiwPath: string): TelemetryConfig;
|
|
17
20
|
export declare function parseTelemetryConfig(content: string): TelemetryConfig;
|
|
18
21
|
export declare function serializeTelemetryConfig(config: TelemetryConfig): string;
|
|
19
22
|
export declare function createTelemetryEvent(command: string | undefined, result: CommandResult, config: TelemetryConfig): TelemetryEvent | undefined;
|
|
20
|
-
export declare function recordConfiguredTelemetry(fs: FileSystem,
|
|
23
|
+
export declare function recordConfiguredTelemetry(fs: FileSystem, aiwPath: string, client: TelemetryClient | undefined, command: string | undefined, result: CommandResult): Promise<void>;
|
package/dist/telemetry.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { loadSettings } from "./settings.js";
|
|
1
2
|
export const DEFAULT_TELEMETRY_CONFIG = {
|
|
2
3
|
schema: 1,
|
|
3
4
|
enabled: false,
|
|
@@ -45,6 +46,41 @@ const TELEMETRY_COMMANDS = new Set([
|
|
|
45
46
|
"verify",
|
|
46
47
|
"verify-package",
|
|
47
48
|
]);
|
|
49
|
+
export function parseTelemetryMapping(raw) {
|
|
50
|
+
if (!raw || typeof raw !== "object")
|
|
51
|
+
throw new Error("telemetry settings must be a mapping");
|
|
52
|
+
const record = raw;
|
|
53
|
+
if (typeof record.enabled !== "boolean")
|
|
54
|
+
throw new Error("telemetry.enabled must be a boolean");
|
|
55
|
+
if (typeof record.include_command !== "boolean")
|
|
56
|
+
throw new Error("telemetry.include_command must be a boolean");
|
|
57
|
+
if (typeof record.include_outcome !== "boolean")
|
|
58
|
+
throw new Error("telemetry.include_outcome must be a boolean");
|
|
59
|
+
return {
|
|
60
|
+
schema: 1,
|
|
61
|
+
enabled: record.enabled,
|
|
62
|
+
includeCommand: record.include_command,
|
|
63
|
+
includeOutcome: record.include_outcome,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export function serializeTelemetryMapping(config) {
|
|
67
|
+
return {
|
|
68
|
+
enabled: config.enabled,
|
|
69
|
+
include_command: config.includeCommand,
|
|
70
|
+
include_outcome: config.includeOutcome,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function loadTelemetryConfig(fs, aiwPath) {
|
|
74
|
+
const settings = loadSettings(fs, aiwPath);
|
|
75
|
+
if (settings !== undefined)
|
|
76
|
+
return "telemetry" in settings
|
|
77
|
+
? parseTelemetryMapping(settings.telemetry)
|
|
78
|
+
: DEFAULT_TELEMETRY_CONFIG;
|
|
79
|
+
const legacyPath = `${aiwPath}/telemetry.yml`;
|
|
80
|
+
if (!fs.exists(legacyPath))
|
|
81
|
+
return DEFAULT_TELEMETRY_CONFIG;
|
|
82
|
+
return parseTelemetryConfig(fs.read(legacyPath));
|
|
83
|
+
}
|
|
48
84
|
export function parseTelemetryConfig(content) {
|
|
49
85
|
const lines = content.split(/\r?\n/);
|
|
50
86
|
if (lines.at(-1) === "")
|
|
@@ -74,11 +110,16 @@ export function createTelemetryEvent(command, result, config) {
|
|
|
74
110
|
...(config.includeOutcome ? { outcome: result.exitCode === 0 ? "success" : "failure" } : {}),
|
|
75
111
|
};
|
|
76
112
|
}
|
|
77
|
-
export async function recordConfiguredTelemetry(fs,
|
|
78
|
-
if (!client
|
|
113
|
+
export async function recordConfiguredTelemetry(fs, aiwPath, client, command, result) {
|
|
114
|
+
if (!client)
|
|
79
115
|
return;
|
|
80
116
|
try {
|
|
81
|
-
const
|
|
117
|
+
const config = loadTelemetryConfig(fs, aiwPath);
|
|
118
|
+
const settings = loadSettings(fs, aiwPath);
|
|
119
|
+
const configured = (settings !== undefined && "telemetry" in settings) || fs.exists(`${aiwPath}/telemetry.yml`);
|
|
120
|
+
if (!configured)
|
|
121
|
+
return;
|
|
122
|
+
const event = createTelemetryEvent(command, result, config);
|
|
82
123
|
if (event)
|
|
83
124
|
await client.record(event);
|
|
84
125
|
}
|
package/dist/workflow.d.ts
CHANGED
|
@@ -2,3 +2,5 @@ import type { CommandResult, FileSystem } from "./types.js";
|
|
|
2
2
|
import type { WorkflowDependencies } from "./services.js";
|
|
3
3
|
export type WorkflowServices = WorkflowDependencies;
|
|
4
4
|
export declare function runCommand(args: string[], root: string, fs: FileSystem, services?: WorkflowServices): Promise<CommandResult>;
|
|
5
|
+
export type ConfirmQuestion = (question: string) => Promise<string>;
|
|
6
|
+
export declare function configureOptionalModels(fs: FileSystem, aiwPath: string, answer: ConfirmQuestion): Promise<string[]>;
|
package/dist/workflow.js
CHANGED
|
@@ -5,8 +5,7 @@ import { detectTarget, isTarget, renderAiInit, renderAiInitPath, renderResourceP
|
|
|
5
5
|
import { loadBundledResources } from "./bundled-resources.js";
|
|
6
6
|
import { assertSafeProjectPath, withSafeProjectWrites, writeProjectFilesAtomically, } from "./files.js";
|
|
7
7
|
import { parseProjectProfile, profileProject } from "./profile.js";
|
|
8
|
-
import {
|
|
9
|
-
import { applyOverrides, parseOverrides } from "./confirmation.js";
|
|
8
|
+
import { applyOverrides } from "./confirmation.js";
|
|
10
9
|
import { mergeFacts } from "./intelligence.js";
|
|
11
10
|
import { createInterpretationRequest } from "./interpreter.js";
|
|
12
11
|
import { recommendCapabilities, resourcesForCapabilities, serializeRecommendations, } from "./recommendations.js";
|
|
@@ -21,8 +20,9 @@ import { validatePackageContract } from "./package-contract.js";
|
|
|
21
20
|
import { parseLock, resolvePackage, serializeLock } from "./lockfile.js";
|
|
22
21
|
import { executeVercelSkills, installVercelSkill, nodeCommandExecutor } from "./vercel-skills.js";
|
|
23
22
|
import { scanProject } from "./scanner.js";
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
23
|
+
import { readFactOverrides, saveSettingOverrides, saveSettingsSection, loadSettings, } from "./settings.js";
|
|
24
|
+
import { buildEvidenceTriageRequest, createOllamaDecisionProvider, DEFAULT_DECISION_CONFIG, DECISION_CONFIG_PATH, loadDecisionConfig, } from "./decision.js";
|
|
25
|
+
import { buildSemanticIndex, createOllamaEmbeddingProvider, DEFAULT_SEMANTIC_CONFIG, loadSemanticConfig, parseSemanticIndex, querySemanticIndex, SEMANTIC_INDEX_PATH, serializeSemanticIndex, } from "./semantic.js";
|
|
26
26
|
import { assertKnownPermissions, assertPermissions, assertPackagePermissions, serializePermissionReview, } from "./package-audit.js";
|
|
27
27
|
import { planPackageUpdate } from "./package-updates.js";
|
|
28
28
|
import { searchRegistry, serializeRegistry } from "./registry.js";
|
|
@@ -42,7 +42,7 @@ import { evaluateQualityGate } from "./quality-gates.js";
|
|
|
42
42
|
import { parseMigrationCheckpoint, planNeutralResourceMigration, readResourceBytes, serializeMigrationPreview, serializeMigrationCheckpoint, validateNeutralResourceDocument, writeResourceBytes, } from "./migration.js";
|
|
43
43
|
import { parsePrivateRegistries, parseTeamPreset, serializePrivateRegistries, serializeTeamPreset, fetchPrivateRegistryClient, } from "./team-configuration.js";
|
|
44
44
|
import { enforcePolicyGate, gitPolicyTracking } from "./policy-gate.js";
|
|
45
|
-
import { DEFAULT_TELEMETRY_CONFIG, parseTelemetryConfig,
|
|
45
|
+
import { DEFAULT_TELEMETRY_CONFIG, loadTelemetryConfig, parseTelemetryConfig, serializeTelemetryMapping, } from "./telemetry.js";
|
|
46
46
|
import { createPluginScaffold, validatePluginDirectory, writePluginScaffold, } from "./plugin-sdk.js";
|
|
47
47
|
import { executeOrchestrationPlan, parseOrchestrationPlan, serializeOrchestrationPreview, serializeOrchestrationResults, validateOrchestrationParallelism, } from "./orchestration.js";
|
|
48
48
|
export async function runCommand(args, root, fs, services = {}) {
|
|
@@ -327,9 +327,22 @@ export async function runCommand(args, root, fs, services = {}) {
|
|
|
327
327
|
pendingWrites.delete(manifestPath);
|
|
328
328
|
pendingWrites.set(manifestPath, `schema: 1\nproject:\n name: ${root.split("/").pop()}\ntarget:\n active: ${target}\npolicies:\n artifact_language: en\n`);
|
|
329
329
|
writeProjectFilesAtomically(fs, root, [...pendingWrites].map(([path, content]) => ({ path, content })), [...BASE_DIRECTORIES.map((directory) => join(aiw, directory)), join(root, ".context/adrs")]);
|
|
330
|
+
let optionalModels = [];
|
|
331
|
+
if (stdin.isTTY) {
|
|
332
|
+
const terminal = createInterface({ input: stdin, output: stdout });
|
|
333
|
+
try {
|
|
334
|
+
optionalModels = await configureOptionalModels(fs, aiw, (question) => terminal.question(question));
|
|
335
|
+
}
|
|
336
|
+
finally {
|
|
337
|
+
terminal.close();
|
|
338
|
+
}
|
|
339
|
+
}
|
|
330
340
|
return {
|
|
331
341
|
exitCode: 0,
|
|
332
|
-
output: `AI Workflow initialized for target: ${target} with only the ai-init skill. Run /ai-init to scan the project and choose which resources to install
|
|
342
|
+
output: `AI Workflow initialized for target: ${target} with only the ai-init skill. Run /ai-init to scan the project and choose which resources to install.` +
|
|
343
|
+
(optionalModels.length
|
|
344
|
+
? ` Optional models enabled: ${optionalModels.join(", ")}. They activate once the models are available.`
|
|
345
|
+
: ""),
|
|
333
346
|
};
|
|
334
347
|
}
|
|
335
348
|
if (command === "telemetry") {
|
|
@@ -339,13 +352,11 @@ export async function runCommand(args, root, fs, services = {}) {
|
|
|
339
352
|
const action = args[1];
|
|
340
353
|
if (!action)
|
|
341
354
|
return { exitCode: 1, error: usage };
|
|
342
|
-
const
|
|
355
|
+
const telemetryConfig = loadTelemetryConfig(fs, aiw);
|
|
343
356
|
if (action === "status") {
|
|
344
357
|
if (args.length !== 2)
|
|
345
358
|
return { exitCode: 1, error: usage };
|
|
346
|
-
const config =
|
|
347
|
-
? parseTelemetryConfig(fs.read(path))
|
|
348
|
-
: DEFAULT_TELEMETRY_CONFIG;
|
|
359
|
+
const config = telemetryConfig;
|
|
349
360
|
return {
|
|
350
361
|
exitCode: 0,
|
|
351
362
|
output: `Telemetry is ${config.enabled ? "enabled" : "disabled"}. Command collection: ${config.includeCommand ? "included" : "excluded"}. Outcome collection: ${config.includeOutcome ? "included" : "excluded"}.`,
|
|
@@ -354,7 +365,7 @@ export async function runCommand(args, root, fs, services = {}) {
|
|
|
354
365
|
if (action === "disable") {
|
|
355
366
|
if (args.length !== 2)
|
|
356
367
|
return { exitCode: 1, error: usage };
|
|
357
|
-
fs
|
|
368
|
+
saveSettingsSection(fs, aiw, "telemetry", serializeTelemetryMapping(DEFAULT_TELEMETRY_CONFIG));
|
|
358
369
|
return { exitCode: 0, output: "Telemetry disabled." };
|
|
359
370
|
}
|
|
360
371
|
if (action === "enable") {
|
|
@@ -364,7 +375,7 @@ export async function runCommand(args, root, fs, services = {}) {
|
|
|
364
375
|
options.length)
|
|
365
376
|
return { exitCode: 1, error: usage };
|
|
366
377
|
const privacyValue = (name) => !options.includes(`--${name}=exclude`);
|
|
367
|
-
fs
|
|
378
|
+
saveSettingsSection(fs, aiw, "telemetry", serializeTelemetryMapping({
|
|
368
379
|
schema: 1,
|
|
369
380
|
enabled: true,
|
|
370
381
|
includeCommand: privacyValue("commands"),
|
|
@@ -415,9 +426,7 @@ export async function runCommand(args, root, fs, services = {}) {
|
|
|
415
426
|
const inferred = await services.interpreter.interpret(createInterpretationRequest(profile, scan.files));
|
|
416
427
|
profile.facts = mergeFacts(profile.facts, inferred);
|
|
417
428
|
}
|
|
418
|
-
const overrides = fs
|
|
419
|
-
? parseOverrides(fs.read(join(aiw, "overrides.yml")))
|
|
420
|
-
: [];
|
|
429
|
+
const overrides = readFactOverrides(fs, aiw);
|
|
421
430
|
const rejectedKeys = new Set(overrides
|
|
422
431
|
.filter((override) => override.action === "reject")
|
|
423
432
|
.map((override) => override.key));
|
|
@@ -902,7 +911,7 @@ export async function runCommand(args, root, fs, services = {}) {
|
|
|
902
911
|
exitCode: 1,
|
|
903
912
|
error: "Usage: aiw confirm --accept key | --reject key | --edit key=value",
|
|
904
913
|
};
|
|
905
|
-
fs
|
|
914
|
+
saveSettingOverrides(fs, aiw, overrides);
|
|
906
915
|
return { exitCode: 0, output: "Fact overrides saved." };
|
|
907
916
|
}
|
|
908
917
|
if (command === "recommend") {
|
|
@@ -1116,6 +1125,7 @@ export async function runCommand(args, root, fs, services = {}) {
|
|
|
1116
1125
|
parsePrivateRegistries(fs.read(join(aiw, "registries.yml")));
|
|
1117
1126
|
if (fs.exists(join(aiw, "telemetry.yml")))
|
|
1118
1127
|
parseTelemetryConfig(fs.read(join(aiw, "telemetry.yml")));
|
|
1128
|
+
loadTelemetryConfig(fs, aiw);
|
|
1119
1129
|
}
|
|
1120
1130
|
catch (error) {
|
|
1121
1131
|
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
@@ -1150,6 +1160,7 @@ export async function runCommand(args, root, fs, services = {}) {
|
|
|
1150
1160
|
"profile.yml",
|
|
1151
1161
|
"lock.yml",
|
|
1152
1162
|
"overrides.yml",
|
|
1163
|
+
"settings.yml",
|
|
1153
1164
|
"recommendations.yml",
|
|
1154
1165
|
"team-preset.yml",
|
|
1155
1166
|
"registries.yml",
|
|
@@ -1781,6 +1792,39 @@ function approvalArtifactPath(aiw, stage) {
|
|
|
1781
1792
|
};
|
|
1782
1793
|
return resolve(aiw, paths[stage]);
|
|
1783
1794
|
}
|
|
1795
|
+
export async function configureOptionalModels(fs, aiwPath, answer) {
|
|
1796
|
+
const enabled = [];
|
|
1797
|
+
const settings = loadSettings(fs, aiwPath) ?? {};
|
|
1798
|
+
if (!("semantic" in settings)) {
|
|
1799
|
+
const response = (await answer(`Enable semantic mapping with a local embedding model (default: ${DEFAULT_SEMANTIC_CONFIG.model} at ${DEFAULT_SEMANTIC_CONFIG.baseUrl})? [y/N] `))
|
|
1800
|
+
.trim()
|
|
1801
|
+
.toLowerCase();
|
|
1802
|
+
if (response === "y" || response === "yes") {
|
|
1803
|
+
saveSettingsSection(fs, aiwPath, "semantic", {
|
|
1804
|
+
enabled: true,
|
|
1805
|
+
model: DEFAULT_SEMANTIC_CONFIG.model,
|
|
1806
|
+
baseUrl: DEFAULT_SEMANTIC_CONFIG.baseUrl,
|
|
1807
|
+
chunkChars: DEFAULT_SEMANTIC_CONFIG.chunkChars,
|
|
1808
|
+
topK: DEFAULT_SEMANTIC_CONFIG.topK,
|
|
1809
|
+
});
|
|
1810
|
+
enabled.push(`semantic (${DEFAULT_SEMANTIC_CONFIG.model})`);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
if (!("decision" in settings)) {
|
|
1814
|
+
const response = (await answer(`Enable advisory decision triage with a local decision model (default: ${DEFAULT_DECISION_CONFIG.model} at ${DEFAULT_DECISION_CONFIG.baseUrl})? [y/N] `))
|
|
1815
|
+
.trim()
|
|
1816
|
+
.toLowerCase();
|
|
1817
|
+
if (response === "y" || response === "yes") {
|
|
1818
|
+
saveSettingsSection(fs, aiwPath, "decision", {
|
|
1819
|
+
enabled: true,
|
|
1820
|
+
model: DEFAULT_DECISION_CONFIG.model,
|
|
1821
|
+
baseUrl: DEFAULT_DECISION_CONFIG.baseUrl,
|
|
1822
|
+
});
|
|
1823
|
+
enabled.push(`decision (${DEFAULT_DECISION_CONFIG.model})`);
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
return enabled;
|
|
1827
|
+
}
|
|
1784
1828
|
function formatConfidence(confidence) {
|
|
1785
1829
|
return confidence === undefined ? "unknown" : confidence.toFixed(3);
|
|
1786
1830
|
}
|
package/docs/semantic-mapping.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Semantic Mapping
|
|
2
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.
|
|
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. Configuration lives in the local user settings file `.aiw/settings.yml` under the `semantic` section; the legacy `.aiw/semantic.yml` file is still read when `settings.yml` does not exist. During an interactive `aiw install`, the installer offers to create this configuration with defaults; answering `y` writes it before `/ai-init` runs. Declining, or installing non-interactively, leaves the section absent.
|
|
4
4
|
|
|
5
5
|
## Configuration
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Add a `semantic` section to `.aiw/settings.yml`:
|
|
8
8
|
|
|
9
9
|
```yaml
|
|
10
10
|
semantic:
|
package/docs/telemetry.md
CHANGED
|
@@ -11,7 +11,7 @@ aiw telemetry enable --commands=exclude --outcomes=include
|
|
|
11
11
|
aiw telemetry disable
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
Preferences are stored in `.aiw/telemetry.yml
|
|
14
|
+
Preferences are stored in the local user settings file `.aiw/settings.yml` under the `telemetry` section (the legacy `.aiw/telemetry.yml` file is still read when `settings.yml` does not exist). Command and outcome collection can be enabled or excluded independently. Disabling telemetry takes effect before the next event can be recorded.
|
|
15
15
|
|
|
16
16
|
Rejected telemetry administration commands never produce telemetry events, even when a previous valid configuration enabled collection. This keeps malformed consent or privacy changes outside the telemetry stream.
|
|
17
17
|
|