@multileaf/ai-workflow 0.1.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.
Files changed (165) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +32 -0
  4. package/dist/adapter-contract.d.ts +13 -0
  5. package/dist/adapter-contract.js +50 -0
  6. package/dist/adapter.d.ts +15 -0
  7. package/dist/adapter.js +62 -0
  8. package/dist/claude-layout.d.ts +9 -0
  9. package/dist/claude-layout.js +23 -0
  10. package/dist/cli.d.ts +4 -0
  11. package/dist/cli.js +30 -0
  12. package/dist/codex-layout.d.ts +10 -0
  13. package/dist/codex-layout.js +24 -0
  14. package/dist/confirmation.d.ts +9 -0
  15. package/dist/confirmation.js +41 -0
  16. package/dist/constants.d.ts +3 -0
  17. package/dist/constants.js +10 -0
  18. package/dist/context-cache.d.ts +8 -0
  19. package/dist/context-cache.js +17 -0
  20. package/dist/context-quality.d.ts +11 -0
  21. package/dist/context-quality.js +27 -0
  22. package/dist/context-retrieval.d.ts +3 -0
  23. package/dist/context-retrieval.js +13 -0
  24. package/dist/context-store.d.ts +9 -0
  25. package/dist/context-store.js +18 -0
  26. package/dist/copilot-layout.d.ts +9 -0
  27. package/dist/copilot-layout.js +24 -0
  28. package/dist/cursor-layout.d.ts +9 -0
  29. package/dist/cursor-layout.js +25 -0
  30. package/dist/dashboard.d.ts +37 -0
  31. package/dist/dashboard.js +251 -0
  32. package/dist/drift.d.ts +2 -0
  33. package/dist/drift.js +10 -0
  34. package/dist/evidence.d.ts +20 -0
  35. package/dist/evidence.js +32 -0
  36. package/dist/files.d.ts +2 -0
  37. package/dist/files.js +58 -0
  38. package/dist/gemini-layout.d.ts +9 -0
  39. package/dist/gemini-layout.js +23 -0
  40. package/dist/generator.d.ts +3 -0
  41. package/dist/generator.js +16 -0
  42. package/dist/intelligence.d.ts +2 -0
  43. package/dist/intelligence.js +13 -0
  44. package/dist/interpreter.d.ts +23 -0
  45. package/dist/interpreter.js +117 -0
  46. package/dist/lockfile.d.ts +12 -0
  47. package/dist/lockfile.js +59 -0
  48. package/dist/manifest.d.ts +15 -0
  49. package/dist/manifest.js +35 -0
  50. package/dist/migration.d.ts +22 -0
  51. package/dist/migration.js +50 -0
  52. package/dist/orchestration.d.ts +30 -0
  53. package/dist/orchestration.js +172 -0
  54. package/dist/organization-policy.d.ts +14 -0
  55. package/dist/organization-policy.js +47 -0
  56. package/dist/package-audit.d.ts +19 -0
  57. package/dist/package-audit.js +28 -0
  58. package/dist/package-contract.d.ts +30 -0
  59. package/dist/package-contract.js +93 -0
  60. package/dist/package-integrity.d.ts +8 -0
  61. package/dist/package-integrity.js +15 -0
  62. package/dist/package-updates.d.ts +11 -0
  63. package/dist/package-updates.js +32 -0
  64. package/dist/plugin-sdk.d.ts +20 -0
  65. package/dist/plugin-sdk.js +243 -0
  66. package/dist/policy-gate.d.ts +6 -0
  67. package/dist/policy-gate.js +20 -0
  68. package/dist/profile.d.ts +23 -0
  69. package/dist/profile.js +123 -0
  70. package/dist/providers.d.ts +20 -0
  71. package/dist/providers.js +67 -0
  72. package/dist/quality-gates.d.ts +2 -0
  73. package/dist/quality-gates.js +40 -0
  74. package/dist/recommendations.d.ts +12 -0
  75. package/dist/recommendations.js +63 -0
  76. package/dist/registry.d.ts +11 -0
  77. package/dist/registry.js +27 -0
  78. package/dist/scanner.d.ts +9 -0
  79. package/dist/scanner.js +83 -0
  80. package/dist/selection.d.ts +3 -0
  81. package/dist/selection.js +11 -0
  82. package/dist/self-validation.d.ts +9 -0
  83. package/dist/self-validation.js +9 -0
  84. package/dist/services.d.ts +42 -0
  85. package/dist/services.js +1 -0
  86. package/dist/team-configuration.d.ts +23 -0
  87. package/dist/team-configuration.js +104 -0
  88. package/dist/telemetry.d.ts +20 -0
  89. package/dist/telemetry.js +95 -0
  90. package/dist/token-usage.d.ts +9 -0
  91. package/dist/token-usage.js +38 -0
  92. package/dist/traceability.d.ts +23 -0
  93. package/dist/traceability.js +51 -0
  94. package/dist/types.d.ts +22 -0
  95. package/dist/types.js +1 -0
  96. package/dist/universal-layout.d.ts +9 -0
  97. package/dist/universal-layout.js +21 -0
  98. package/dist/vercel-skills.d.ts +17 -0
  99. package/dist/vercel-skills.js +49 -0
  100. package/dist/workflow.d.ts +4 -0
  101. package/dist/workflow.js +1130 -0
  102. package/docs/README.md +20 -0
  103. package/docs/adapter-model.md +16 -0
  104. package/docs/architecture.md +66 -0
  105. package/docs/backlog.md +93 -0
  106. package/docs/orchestration.md +50 -0
  107. package/docs/package-model.md +53 -0
  108. package/docs/plugin-authoring.md +31 -0
  109. package/docs/product-requirements.md +57 -0
  110. package/docs/product-vision.md +26 -0
  111. package/docs/project-intelligence.md +59 -0
  112. package/docs/quality.md +32 -0
  113. package/docs/release-backlog.md +7 -0
  114. package/docs/release-process.md +38 -0
  115. package/docs/security.md +18 -0
  116. package/docs/telemetry.md +28 -0
  117. package/docs/token-efficiency.md +16 -0
  118. package/package.json +76 -0
  119. package/resources/README.md +4 -0
  120. package/resources/agents/adapter-engineer.md +4 -0
  121. package/resources/agents/documentation-writer.md +4 -0
  122. package/resources/agents/quality-reviewer.md +4 -0
  123. package/resources/agents/requirements-analyst.md +4 -0
  124. package/resources/agents/security-reviewer.md +4 -0
  125. package/resources/agents/technical-architect.md +4 -0
  126. package/resources/agents/test-engineer.md +4 -0
  127. package/resources/agents/typescript-engineer.md +4 -0
  128. package/resources/hooks/post-change.md +4 -0
  129. package/resources/hooks/post-implementation.md +4 -0
  130. package/resources/hooks/pre-commit.md +4 -0
  131. package/resources/hooks/pre-implementation.md +4 -0
  132. package/resources/hooks/pre-release.md +4 -0
  133. package/resources/package.yaml +36 -0
  134. package/resources/rules/architecture-policy.md +4 -0
  135. package/resources/rules/completion-validation.md +19 -0
  136. package/resources/rules/dependency-policy.md +4 -0
  137. package/resources/rules/documentation-policy.md +4 -0
  138. package/resources/rules/generated-files.md +4 -0
  139. package/resources/rules/language-policy.md +4 -0
  140. package/resources/rules/quality-gates.md +4 -0
  141. package/resources/rules/security-policy.md +4 -0
  142. package/resources/rules/self-validation.md +12 -0
  143. package/resources/rules/tdd-policy.md +4 -0
  144. package/resources/skills/adapter-development.md +9 -0
  145. package/resources/skills/brainstorming.md +9 -0
  146. package/resources/skills/code-review.md +9 -0
  147. package/resources/skills/documentation-maintenance.md +9 -0
  148. package/resources/skills/implementation-planning.md +9 -0
  149. package/resources/skills/project-init.md +9 -0
  150. package/resources/skills/requirements-specification.md +9 -0
  151. package/resources/skills/security-review.md +9 -0
  152. package/resources/skills/skill-authoring.md +9 -0
  153. package/resources/skills/tdd-development.md +9 -0
  154. package/resources/skills/technical-design.md +9 -0
  155. package/resources/skills/verification.md +9 -0
  156. package/resources/templates/brainstorm.md +16 -0
  157. package/resources/templates/decision-record.md +12 -0
  158. package/resources/templates/implementation-plan.md +12 -0
  159. package/resources/templates/migration-report.md +14 -0
  160. package/resources/templates/security-review.md +14 -0
  161. package/resources/templates/specification.md +23 -0
  162. package/resources/templates/task.md +14 -0
  163. package/resources/templates/technical-design.md +14 -0
  164. package/resources/templates/test-plan.md +12 -0
  165. package/resources/templates/verification-report.md +12 -0
@@ -0,0 +1,63 @@
1
+ const CATALOG = [
2
+ {
3
+ id: "tdd-development",
4
+ provider: "multileaf",
5
+ rationale: "The project uses automated tests and should preserve test-first development.",
6
+ matches: (profile) => profile.testing
7
+ ? { evidence: [`testing:${profile.testing.name}`], confidence: 1 }
8
+ : undefined,
9
+ },
10
+ {
11
+ id: "typescript-quality",
12
+ provider: "multileaf",
13
+ rationale: "TypeScript is present and benefits from strict, maintainable code-quality rules.",
14
+ matches: (profile) => profile.runtime.languages.includes("typescript")
15
+ ? { evidence: ["language:typescript"], confidence: 1 }
16
+ : undefined,
17
+ },
18
+ {
19
+ id: "vitest-testing",
20
+ provider: "multileaf",
21
+ rationale: "Vitest is configured and can be integrated into the verification workflow.",
22
+ matches: (profile) => profile.testing?.name === "vitest"
23
+ ? { evidence: ["testing:vitest"], confidence: 1 }
24
+ : undefined,
25
+ },
26
+ {
27
+ id: "react-best-practices",
28
+ provider: "vercel-skills",
29
+ rationale: "React is detected; framework-specific guidance can improve component design and performance.",
30
+ matches: (profile) => profile.frameworks.includes("react")
31
+ ? { evidence: ["framework:react"], confidence: 1 }
32
+ : undefined,
33
+ },
34
+ {
35
+ id: "nextjs-development",
36
+ provider: "multileaf",
37
+ rationale: "Next.js is detected; project-specific conventions can guide routing and rendering decisions.",
38
+ matches: (profile) => profile.frameworks.includes("nextjs")
39
+ ? { evidence: ["framework:nextjs"], confidence: 1 }
40
+ : undefined,
41
+ },
42
+ ];
43
+ export function recommendCapabilities(profile) {
44
+ return CATALOG.flatMap((capability) => {
45
+ const match = capability.matches(profile);
46
+ return match
47
+ ? [
48
+ {
49
+ id: capability.id,
50
+ provider: capability.provider,
51
+ confidence: match.confidence,
52
+ rationale: capability.rationale,
53
+ evidence: match.evidence,
54
+ permissions: capability.permissions ?? [],
55
+ conflicts: [],
56
+ },
57
+ ]
58
+ : [];
59
+ });
60
+ }
61
+ export function serializeRecommendations(items, selected = []) {
62
+ return `schema: 1\nrecommendations:\n${items.map((item) => ` - id: ${item.id}\n provider: ${item.provider}\n confidence: ${item.confidence}\n selected: ${selected.includes(item.id)}\n evidence: [${item.evidence.map((evidence) => `"${evidence}"`).join(", ")}]\n rationale: ${item.rationale}`).join("\n")}\n`;
63
+ }
@@ -0,0 +1,11 @@
1
+ export type RegistryPackage = {
2
+ id: string;
3
+ version: string;
4
+ description: string;
5
+ provider: "local" | "git" | "vercel-skills" | "private-registry";
6
+ source: string;
7
+ permissions: string[];
8
+ };
9
+ export declare const CURATED_REGISTRY: readonly RegistryPackage[];
10
+ export declare function searchRegistry(query?: string): RegistryPackage[];
11
+ export declare function serializeRegistry(packages: readonly RegistryPackage[]): string;
@@ -0,0 +1,27 @@
1
+ export const CURATED_REGISTRY = [
2
+ {
3
+ id: "multileaf/aiw-self-hosting",
4
+ version: "0.1.0",
5
+ description: "Core skills and policies for developing with AI Workflow.",
6
+ provider: "local",
7
+ source: "./resources",
8
+ permissions: [],
9
+ },
10
+ {
11
+ id: "vercel/react-best-practices",
12
+ version: "latest",
13
+ description: "React and Next.js performance guidance from Vercel.",
14
+ provider: "vercel-skills",
15
+ source: "vercel-labs/agent-skills@vercel-react-best-practices",
16
+ permissions: ["network:external"],
17
+ },
18
+ ];
19
+ export function searchRegistry(query = "") {
20
+ const normalized = query.trim().toLowerCase();
21
+ return CURATED_REGISTRY.filter((pkg) => [pkg.id, pkg.description, pkg.provider].some((field) => field.toLowerCase().includes(normalized))).map((pkg) => ({ ...pkg, permissions: [...pkg.permissions] }));
22
+ }
23
+ export function serializeRegistry(packages) {
24
+ return packages
25
+ .map((pkg) => `${pkg.id}@${pkg.version} [${pkg.provider}] permissions: ${pkg.permissions.join(",") || "none"} - ${pkg.description}`)
26
+ .join("\n");
27
+ }
@@ -0,0 +1,9 @@
1
+ export type ProjectEvidence = {
2
+ fact: string;
3
+ source: string;
4
+ };
5
+ export type ProjectScan = {
6
+ files: string[];
7
+ evidence: ProjectEvidence[];
8
+ };
9
+ export declare function scanProject(root: string): Promise<ProjectScan>;
@@ -0,0 +1,83 @@
1
+ import { lstat, readFile, readdir } from "node:fs/promises";
2
+ import { basename, relative, resolve } from "node:path";
3
+ const DEFAULT_IGNORES = new Set([
4
+ ".git",
5
+ "node_modules",
6
+ ".aiw",
7
+ ".agents",
8
+ ".claude",
9
+ ".cursor",
10
+ "dist",
11
+ "coverage",
12
+ ]);
13
+ const SENSITIVE_NAMES = /^(\.env(?:\..*)?|.*\.(pem|key|p12|pfx|secret))$/i;
14
+ function matchesIgnore(path, patterns) {
15
+ const name = basename(path);
16
+ return patterns.some((pattern) => {
17
+ const normalized = pattern.replace(/^\//, "").replace(/\/$/, "");
18
+ if (normalized.includes("*")) {
19
+ const expression = new RegExp(`^${normalized.split("*").map(escapeRegExp).join(".*")}$`);
20
+ return expression.test(path) || expression.test(name);
21
+ }
22
+ return path === normalized || path.startsWith(`${normalized}/`) || name === normalized;
23
+ });
24
+ }
25
+ function escapeRegExp(value) {
26
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
27
+ }
28
+ async function ignoredPatterns(root) {
29
+ try {
30
+ const content = await readFile(resolve(root, ".gitignore"), "utf8");
31
+ return content
32
+ .split(/\r?\n/)
33
+ .map((line) => line.trim())
34
+ .filter((line) => line && !line.startsWith("#"));
35
+ }
36
+ catch {
37
+ return [];
38
+ }
39
+ }
40
+ async function collect(root, current, patterns, files) {
41
+ const entries = await readdir(current, { withFileTypes: true });
42
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
43
+ const absolute = resolve(current, entry.name);
44
+ const relativePath = relative(root, absolute).split("\\").join("/");
45
+ if (DEFAULT_IGNORES.has(entry.name) ||
46
+ SENSITIVE_NAMES.test(entry.name) ||
47
+ matchesIgnore(relativePath, patterns))
48
+ continue;
49
+ if (entry.isSymbolicLink()) {
50
+ const link = await lstat(absolute);
51
+ if (link.isSymbolicLink())
52
+ continue;
53
+ }
54
+ if (entry.isDirectory())
55
+ await collect(root, absolute, patterns, files);
56
+ else if (entry.isFile())
57
+ files.push(relativePath);
58
+ }
59
+ }
60
+ function detectEvidence(files) {
61
+ const evidence = [];
62
+ for (const file of files) {
63
+ if (/\.(ts|tsx)$/.test(file))
64
+ evidence.push({ fact: "language.typescript", source: file });
65
+ if (/\.(js|jsx|mjs|cjs)$/.test(file))
66
+ evidence.push({ fact: "language.javascript", source: file });
67
+ if (basename(file).startsWith("eslint.config") || file === ".eslintrc.json")
68
+ evidence.push({ fact: "tool.eslint", source: file });
69
+ if (file === "package.json")
70
+ evidence.push({ fact: "package-manager.npm", source: file });
71
+ if (file === "pnpm-lock.yaml")
72
+ evidence.push({ fact: "package-manager.pnpm", source: file });
73
+ if (file === "yarn.lock")
74
+ evidence.push({ fact: "package-manager.yarn", source: file });
75
+ }
76
+ return evidence;
77
+ }
78
+ export async function scanProject(root) {
79
+ const projectRoot = resolve(root);
80
+ const files = [];
81
+ await collect(projectRoot, projectRoot, await ignoredPatterns(projectRoot), files);
82
+ return { files, evidence: detectEvidence(files) };
83
+ }
@@ -0,0 +1,3 @@
1
+ import type { CapabilityRecommendation } from "./recommendations.js";
2
+ export type Answer = (question: string) => Promise<string>;
3
+ export declare function selectRecommendations(items: CapabilityRecommendation[], answer: Answer, selectedIds?: string[]): Promise<string[]>;
@@ -0,0 +1,11 @@
1
+ export async function selectRecommendations(items, answer, selectedIds) {
2
+ if (selectedIds)
3
+ return selectedIds.filter((id) => items.some((item) => item.id === id));
4
+ const selected = [];
5
+ for (const item of items) {
6
+ const response = (await answer(`Install ${item.id}? [Y/n] `)).trim().toLowerCase();
7
+ if (response === "" || response === "y" || response === "yes")
8
+ selected.push(item.id);
9
+ }
10
+ return selected;
11
+ }
@@ -0,0 +1,9 @@
1
+ export type SelfValidationEvidence = {
2
+ ticket: string;
3
+ command: string;
4
+ exitCode: number;
5
+ requiredState: string[];
6
+ generatedArtifacts: string[];
7
+ };
8
+ export declare function validateTicketId(ticket: string): void;
9
+ export declare function serializeSelfValidationEvidence(evidence: SelfValidationEvidence): string;
@@ -0,0 +1,9 @@
1
+ export function validateTicketId(ticket) {
2
+ if (!/^[A-Z]+-\d{3}$/.test(ticket))
3
+ throw new Error("Self-validation ticket must match TYPE-000, for example FND-009.");
4
+ }
5
+ export function serializeSelfValidationEvidence(evidence) {
6
+ const state = [...evidence.requiredState].sort();
7
+ const artifacts = [...evidence.generatedArtifacts].sort();
8
+ return `schema: 1\nticket: ${evidence.ticket}\ncommand: ${evidence.command}\nexit_code: ${evidence.exitCode}\nrequired_state:\n${state.map((path) => ` - ${path}`).join("\n")}\ngenerated_artifacts:\n${artifacts.length ? artifacts.map((path) => ` - ${path}`).join("\n") : " - none"}\nresult: ${evidence.exitCode === 0 ? "pass" : "fail"}\n`;
9
+ }
@@ -0,0 +1,42 @@
1
+ import type { ProjectProfile } from "./profile.js";
2
+ import type { ProjectScan } from "./scanner.js";
3
+ import type { CapabilityRecommendation } from "./recommendations.js";
4
+ import type { Interpreter } from "./interpreter.js";
5
+ import type { CommandExecutor } from "./vercel-skills.js";
6
+ import type { CommandResult, FileSystem } from "./types.js";
7
+ import type { PackageSourceLoader } from "./providers.js";
8
+ import type { PrivateRegistryClient } from "./team-configuration.js";
9
+ import type { PolicyTracking } from "./policy-gate.js";
10
+ import type { TelemetryClient } from "./telemetry.js";
11
+ import type { DashboardHandle } from "./dashboard.js";
12
+ import type { AgentOrchestrator } from "./orchestration.js";
13
+ export interface ProjectScanner {
14
+ scan(root: string): Promise<ProjectScan>;
15
+ }
16
+ export interface ProfileService {
17
+ profile(root: string): Promise<ProjectProfile>;
18
+ }
19
+ export interface RecommendationService {
20
+ recommend(profile: ProjectProfile): CapabilityRecommendation[];
21
+ }
22
+ export interface WorkflowDependencies {
23
+ scanner?: ProjectScanner;
24
+ profiler?: ProfileService;
25
+ recommender?: RecommendationService;
26
+ interpreter?: Interpreter;
27
+ externalSkills?: CommandExecutor;
28
+ packageSources?: PackageSourceLoader;
29
+ selfValidation?: CommandExecutor;
30
+ privateRegistries?: PrivateRegistryClient;
31
+ environment?: Readonly<Record<string, string | undefined>>;
32
+ policyTracking?: PolicyTracking;
33
+ telemetry?: TelemetryClient;
34
+ dashboard?: {
35
+ start(root: string, port: number): Promise<DashboardHandle>;
36
+ };
37
+ orchestrator?: AgentOrchestrator;
38
+ }
39
+ export interface WorkflowService {
40
+ execute(args: string[], root: string, fs: FileSystem): Promise<CommandResult>;
41
+ }
42
+ export type ServiceFactory<T> = (dependencies: WorkflowDependencies) => T;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ export type TeamPreset = {
2
+ schema: 1;
3
+ name: string;
4
+ packages: string[];
5
+ registries: string[];
6
+ };
7
+ export type PrivateRegistry = {
8
+ name: string;
9
+ url: string;
10
+ tokenEnv: string;
11
+ };
12
+ export type PrivateRegistryConfiguration = {
13
+ schema: 1;
14
+ registries: PrivateRegistry[];
15
+ };
16
+ export interface PrivateRegistryClient {
17
+ search(registry: PrivateRegistry, query: string, token: string): Promise<unknown>;
18
+ }
19
+ export declare const fetchPrivateRegistryClient: PrivateRegistryClient;
20
+ export declare function parseTeamPreset(content: string): TeamPreset;
21
+ export declare function serializeTeamPreset(preset: TeamPreset): string;
22
+ export declare function parsePrivateRegistries(content: string): PrivateRegistryConfiguration;
23
+ export declare function serializePrivateRegistries(configuration: PrivateRegistryConfiguration): string;
@@ -0,0 +1,104 @@
1
+ export const fetchPrivateRegistryClient = {
2
+ async search(registry, query, token) {
3
+ const endpoint = new URL(registry.url);
4
+ endpoint.searchParams.set("q", query);
5
+ const response = await fetch(endpoint, {
6
+ headers: { authorization: `Bearer ${token}`, accept: "application/json" },
7
+ });
8
+ if (!response.ok)
9
+ throw new Error(`Private registry ${registry.name} returned HTTP ${response.status}.`);
10
+ return response.json();
11
+ },
12
+ };
13
+ export function parseTeamPreset(content) {
14
+ assertSchema(content, "Team preset");
15
+ const name = scalar(content, "name");
16
+ if (!name)
17
+ throw new Error("Team preset name is required.");
18
+ return {
19
+ schema: 1,
20
+ name,
21
+ packages: normalizedList(content, "packages", "Team preset"),
22
+ registries: normalizedList(content, "registries", "Team preset"),
23
+ };
24
+ }
25
+ export function serializeTeamPreset(preset) {
26
+ return `schema: 1\nname: ${preset.name}\npackages: [${preset.packages.join(", ")}]\nregistries: [${preset.registries.join(", ")}]\n`;
27
+ }
28
+ export function parsePrivateRegistries(content) {
29
+ assertSchema(content, "Private registry configuration");
30
+ const lines = content.split(/\r?\n/).filter((line) => line.trim());
31
+ const entryLines = lines.slice(2);
32
+ if (!/^schema:[ \t]*1[ \t]*$/.test(lines[0] ?? "") || !/^registries:[ \t]*$/.test(lines[1] ?? ""))
33
+ throw new Error("Private registry configuration contains unsupported or malformed fields.");
34
+ if (!entryLines.every((line) => /^[ \t]+-[ \t]+\{[^\r\n]+\}[ \t]*$/.test(line)))
35
+ throw new Error("Private registry configuration contains unsupported or malformed fields.");
36
+ const entries = entryLines.map((line) => parseRegistryEntry(line.slice(line.indexOf("{") + 1, line.lastIndexOf("}"))));
37
+ if (!entries.length)
38
+ throw new Error("Private registry configuration registries must not be empty.");
39
+ const names = new Set();
40
+ for (const registry of entries) {
41
+ if (names.has(registry.name))
42
+ throw new Error(`Duplicate private registry: ${registry.name}`);
43
+ names.add(registry.name);
44
+ }
45
+ return { schema: 1, registries: entries.sort((a, b) => a.name.localeCompare(b.name)) };
46
+ }
47
+ export function serializePrivateRegistries(configuration) {
48
+ return `schema: 1\nregistries:\n${configuration.registries
49
+ .map(({ name, url, tokenEnv }) => ` - { name: ${name}, url: ${url}, token_env: ${tokenEnv} }`)
50
+ .join("\n")}\n`;
51
+ }
52
+ function parseRegistryEntry(content) {
53
+ const pairs = content.split(",").map((part) => {
54
+ const separator = part.indexOf(":");
55
+ if (separator < 1)
56
+ throw new Error("Private registry entries must use key: value fields.");
57
+ return [part.slice(0, separator).trim(), part.slice(separator + 1).trim()];
58
+ });
59
+ if (new Set(pairs.map(([key]) => key)).size !== pairs.length)
60
+ throw new Error("Private registry entries must not contain duplicate fields.");
61
+ const fields = new Map(pairs);
62
+ const name = fields.get("name") ?? "";
63
+ const url = fields.get("url") ?? "";
64
+ const tokenEnv = fields.get("token_env") ?? "";
65
+ if (!name || !url || !tokenEnv)
66
+ throw new Error("Private registry entries require name, url, and token_env.");
67
+ if (fields.has("token") || fields.has("password") || fields.size !== 3)
68
+ throw new Error("Private registry credentials must use token_env; secret values are forbidden.");
69
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(name))
70
+ throw new Error("Invalid private registry name.");
71
+ let parsed;
72
+ try {
73
+ parsed = new URL(url);
74
+ }
75
+ catch {
76
+ throw new Error("Invalid private registry URL.");
77
+ }
78
+ if (parsed.protocol !== "https:")
79
+ throw new Error("Private registry URLs must use HTTPS.");
80
+ if (parsed.username || parsed.password || parsed.search || parsed.hash)
81
+ throw new Error("Private registry URLs must not contain credentials, query, or fragment data.");
82
+ if (!/^[A-Z_][A-Z0-9_]*$/.test(tokenEnv))
83
+ throw new Error("Invalid private registry token_env.");
84
+ return { name, url: parsed.toString().replace(/\/$/, ""), tokenEnv };
85
+ }
86
+ function assertSchema(content, label) {
87
+ if (scalar(content, "schema") !== "1")
88
+ throw new Error(`${label} schema must be version 1.`);
89
+ }
90
+ function scalar(content, key) {
91
+ return content.match(new RegExp(`^${key}:[ \\t]*([^\\r\\n]*)$`, "m"))?.[1]?.trim() ?? "";
92
+ }
93
+ function normalizedList(content, key, label) {
94
+ const value = scalar(content, key);
95
+ if (!value.startsWith("[") || !value.endsWith("]"))
96
+ throw new Error(`${label} ${key} must be an inline list.`);
97
+ return [
98
+ ...new Set(value
99
+ .slice(1, -1)
100
+ .split(",")
101
+ .map((item) => item.trim())
102
+ .filter(Boolean)),
103
+ ].sort();
104
+ }
@@ -0,0 +1,20 @@
1
+ import type { CommandResult, FileSystem } from "./types.js";
2
+ export type TelemetryConfig = {
3
+ schema: 1;
4
+ enabled: boolean;
5
+ includeCommand: boolean;
6
+ includeOutcome: boolean;
7
+ };
8
+ export type TelemetryEvent = {
9
+ schema: 1;
10
+ command?: string;
11
+ outcome?: "success" | "failure";
12
+ };
13
+ export interface TelemetryClient {
14
+ record(event: TelemetryEvent): Promise<void>;
15
+ }
16
+ export declare const DEFAULT_TELEMETRY_CONFIG: TelemetryConfig;
17
+ export declare function parseTelemetryConfig(content: string): TelemetryConfig;
18
+ export declare function serializeTelemetryConfig(config: TelemetryConfig): string;
19
+ export declare function createTelemetryEvent(command: string | undefined, result: CommandResult, config: TelemetryConfig): TelemetryEvent | undefined;
20
+ export declare function recordConfiguredTelemetry(fs: FileSystem, configPath: string, client: TelemetryClient | undefined, command: string | undefined, result: CommandResult): Promise<void>;
@@ -0,0 +1,95 @@
1
+ export const DEFAULT_TELEMETRY_CONFIG = {
2
+ schema: 1,
3
+ enabled: false,
4
+ includeCommand: true,
5
+ includeOutcome: true,
6
+ };
7
+ const TELEMETRY_COMMANDS = new Set([
8
+ "adr",
9
+ "audit-package",
10
+ "brainstorm",
11
+ "capabilities",
12
+ "confirm",
13
+ "context",
14
+ "context-quality",
15
+ "context-summary",
16
+ "doctor",
17
+ "gate",
18
+ "generate",
19
+ "install",
20
+ "organization-policy",
21
+ "plan",
22
+ "plugin",
23
+ "policy-check",
24
+ "preset",
25
+ "recommend",
26
+ "registry",
27
+ "repair",
28
+ "resolve",
29
+ "rollback",
30
+ "scan",
31
+ "self-validate",
32
+ "skills",
33
+ "spec",
34
+ "status",
35
+ "sync",
36
+ "target",
37
+ "telemetry",
38
+ "token-usage",
39
+ "trace",
40
+ "uninstall",
41
+ "update",
42
+ "validate",
43
+ "verify",
44
+ "verify-package",
45
+ ]);
46
+ export function parseTelemetryConfig(content) {
47
+ const lines = content.split(/\r?\n/);
48
+ if (lines.at(-1) === "")
49
+ lines.pop();
50
+ if (lines[0] !== "schema: 1")
51
+ throw new Error("Telemetry schema must be version 1.");
52
+ if (lines.length !== 5)
53
+ throw new Error("Telemetry configuration structure is invalid.");
54
+ if (lines[2] !== "privacy:")
55
+ throw new Error("Telemetry privacy section is required.");
56
+ return {
57
+ schema: 1,
58
+ enabled: booleanLine(lines[1], /^enabled: (true|false)$/, "enabled"),
59
+ includeCommand: booleanLine(lines[3], /^[ ]{2}include_command: (true|false)$/, "include_command"),
60
+ includeOutcome: booleanLine(lines[4], /^[ ]{2}include_outcome: (true|false)$/, "include_outcome"),
61
+ };
62
+ }
63
+ export function serializeTelemetryConfig(config) {
64
+ return `schema: 1\nenabled: ${config.enabled}\nprivacy:\n include_command: ${config.includeCommand}\n include_outcome: ${config.includeOutcome}\n`;
65
+ }
66
+ export function createTelemetryEvent(command, result, config) {
67
+ if (!config.enabled)
68
+ return undefined;
69
+ return {
70
+ schema: 1,
71
+ ...(config.includeCommand ? { command: safeCommand(command) } : {}),
72
+ ...(config.includeOutcome ? { outcome: result.exitCode === 0 ? "success" : "failure" } : {}),
73
+ };
74
+ }
75
+ export async function recordConfiguredTelemetry(fs, configPath, client, command, result) {
76
+ if (!client || !fs.exists(configPath))
77
+ return;
78
+ try {
79
+ const event = createTelemetryEvent(command, result, parseTelemetryConfig(fs.read(configPath)));
80
+ if (event)
81
+ await client.record(event);
82
+ }
83
+ catch {
84
+ // Telemetry must never affect the requested workflow operation.
85
+ }
86
+ }
87
+ function safeCommand(command) {
88
+ return command && TELEMETRY_COMMANDS.has(command) ? command : "unknown";
89
+ }
90
+ function booleanLine(line, pattern, key) {
91
+ const parsed = line?.match(pattern)?.[1];
92
+ if (!parsed)
93
+ throw new Error(`Telemetry field '${key}' must be true or false.`);
94
+ return parsed === "true";
95
+ }
@@ -0,0 +1,9 @@
1
+ export type TokenUsage = {
2
+ stage: string;
3
+ budget: number;
4
+ used: number;
5
+ };
6
+ export declare function validateTokenUsage(usage: TokenUsage): TokenUsage;
7
+ export declare function parseTokenUsage(content: string): TokenUsage[];
8
+ export declare function serializeTokenUsage(entries: TokenUsage[]): string;
9
+ export declare function recordTokenUsage(entries: TokenUsage[], usage: TokenUsage): TokenUsage[];
@@ -0,0 +1,38 @@
1
+ export function validateTokenUsage(usage) {
2
+ if (!usage.stage.trim())
3
+ throw new Error("Token usage stage must not be empty.");
4
+ for (const [name, value] of [
5
+ ["budget", usage.budget],
6
+ ["used", usage.used],
7
+ ]) {
8
+ if (!Number.isInteger(value) || value < 0)
9
+ throw new Error(`Token ${name} must be a non-negative integer.`);
10
+ }
11
+ if (usage.used > usage.budget)
12
+ throw new Error(`Token budget exceeded for stage: ${usage.stage}`);
13
+ return usage;
14
+ }
15
+ export function parseTokenUsage(content) {
16
+ if (!content.trim())
17
+ return [];
18
+ return content
19
+ .split("\n")
20
+ .slice(1)
21
+ .filter(Boolean)
22
+ .map((line) => {
23
+ const [stage, budget, used] = line.split("|");
24
+ return validateTokenUsage({ stage, budget: Number(budget), used: Number(used) });
25
+ });
26
+ }
27
+ export function serializeTokenUsage(entries) {
28
+ return ([
29
+ "schema: 1",
30
+ ...entries
31
+ .sort((a, b) => a.stage.localeCompare(b.stage))
32
+ .map((entry) => `${entry.stage}|${entry.budget}|${entry.used}`),
33
+ ].join("\n") + "\n");
34
+ }
35
+ export function recordTokenUsage(entries, usage) {
36
+ validateTokenUsage(usage);
37
+ return [...entries.filter((entry) => entry.stage !== usage.stage), usage];
38
+ }
@@ -0,0 +1,23 @@
1
+ export type TraceabilityLink = {
2
+ requirement: string;
3
+ decisions: string[];
4
+ tasks: string[];
5
+ code: string[];
6
+ tests: string[];
7
+ evidence: string[];
8
+ };
9
+ export type TraceabilityGraph = {
10
+ schema: 1;
11
+ links: TraceabilityLink[];
12
+ };
13
+ type TraceabilityInput = {
14
+ specification: string;
15
+ plan: string;
16
+ decisions: {
17
+ id: string;
18
+ content: string;
19
+ }[];
20
+ };
21
+ export declare function buildTraceabilityGraph(input: TraceabilityInput): TraceabilityGraph;
22
+ export declare function serializeTraceabilityGraph(graph: TraceabilityGraph, requirement?: string): string;
23
+ export {};
@@ -0,0 +1,51 @@
1
+ const uniqueSorted = (values) => [...new Set(values)].sort();
2
+ function valuesForField(block, field) {
3
+ const expression = new RegExp(`^\\s*-?\\s*${field}:\\s*(.+)$`, "gim");
4
+ return [...block.matchAll(expression)].map((match) => match[1].trim());
5
+ }
6
+ export function buildTraceabilityGraph(input) {
7
+ const requirements = uniqueSorted(input.specification.match(/REQ-\d+/g) ?? []);
8
+ const taskStarts = [...input.plan.matchAll(/(?:^|\n)\s*-?\s*(?:\[[ x]\]\s*)?(TASK-\d+)/gi)];
9
+ const tasks = taskStarts.map((match, index) => ({
10
+ id: match[1],
11
+ block: input.plan.slice(match.index, taskStarts[index + 1]?.index ?? input.plan.length),
12
+ }));
13
+ const globalValidation = uniqueSorted([
14
+ ...valuesForField(input.plan, "Validation"),
15
+ ...(input.plan.match(/npm (?:run )?[\w:-]+/g) ?? []),
16
+ ]);
17
+ return {
18
+ schema: 1,
19
+ links: requirements.map((requirement) => {
20
+ const linkedTasks = tasks.filter(({ block }) => block.includes(requirement));
21
+ return {
22
+ requirement,
23
+ decisions: uniqueSorted(input.decisions
24
+ .filter(({ content }) => content.includes(requirement))
25
+ .map(({ id }) => id)),
26
+ tasks: uniqueSorted(linkedTasks.map(({ id }) => id)),
27
+ code: uniqueSorted(linkedTasks.flatMap(({ block }) => valuesForField(block, "Code"))),
28
+ tests: uniqueSorted(linkedTasks.flatMap(({ block }) => valuesForField(block, "Tests"))),
29
+ evidence: uniqueSorted([
30
+ ...linkedTasks.flatMap(({ block }) => valuesForField(block, "Evidence")),
31
+ ...(linkedTasks.length > 0 ? globalValidation : []),
32
+ ]),
33
+ };
34
+ }),
35
+ };
36
+ }
37
+ function yamlList(values) {
38
+ return `[${values
39
+ .map((value) => (/^(?:REQ|ADR|TASK)-\d+$/.test(value) ? value : JSON.stringify(value)))
40
+ .join(", ")}]`;
41
+ }
42
+ export function serializeTraceabilityGraph(graph, requirement) {
43
+ const links = requirement
44
+ ? graph.links.filter((link) => link.requirement === requirement)
45
+ : graph.links;
46
+ if (requirement && links.length === 0)
47
+ throw new Error(`Requirement not found: ${requirement}`);
48
+ return `schema: 1\nlinks:\n${links
49
+ .map((link) => ` - requirement: ${link.requirement}\n decisions: ${yamlList(link.decisions)}\n tasks: ${yamlList(link.tasks)}\n code: ${yamlList(link.code)}\n tests: ${yamlList(link.tests)}\n evidence: ${yamlList(link.evidence)}`)
50
+ .join("\n")}\n`;
51
+ }