@bvdm/delano 0.1.5 → 0.1.8

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 (145) hide show
  1. package/.delano/README.md +7 -0
  2. package/.delano/viewer/README.md +19 -0
  3. package/.delano/viewer/public/app.js +818 -0
  4. package/.delano/viewer/public/explorer.svg +3 -0
  5. package/.delano/viewer/public/index.html +21 -0
  6. package/.delano/viewer/public/markdown.svg +6 -0
  7. package/.delano/viewer/public/styles.css +1042 -0
  8. package/.delano/viewer/public/vscode.svg +24 -0
  9. package/.delano/viewer/server.js +389 -0
  10. package/HANDBOOK.md +66 -45
  11. package/README.md +21 -2
  12. package/assets/install-manifest.json +112 -23
  13. package/assets/payload/.agents/README.md +31 -6
  14. package/assets/payload/.agents/adapters/claude/README.md +22 -3
  15. package/assets/payload/.agents/adapters/codex/README.md +22 -3
  16. package/assets/payload/.agents/adapters/opencode/README.md +22 -3
  17. package/assets/payload/.agents/adapters/pi/README.md +22 -3
  18. package/assets/payload/.agents/common/log-safety.js +55 -0
  19. package/assets/payload/.agents/eval-fixtures/skill-output/invalid/missing-evidence/output.json +6 -0
  20. package/assets/payload/.agents/eval-fixtures/skill-output/valid/summary/output.json +7 -0
  21. package/assets/payload/.agents/fixtures/github/status-snapshot.json +6 -0
  22. package/assets/payload/.agents/fixtures/linear/issue-snapshot.json +6 -0
  23. package/assets/payload/.agents/hooks/bash-worktree-fix.sh +2 -1
  24. package/assets/payload/.agents/hooks/post-tool-logger.js +2 -1
  25. package/assets/payload/.agents/hooks/session-tracker.js +0 -0
  26. package/assets/payload/.agents/hooks/user-prompt-logger.js +17 -1
  27. package/assets/payload/.agents/logs/delivery-metrics.md +22 -0
  28. package/assets/payload/.agents/logs/schema.md +20 -1
  29. package/assets/payload/.agents/rules/delivery-modes.md +17 -0
  30. package/assets/payload/.agents/schemas/README.md +22 -0
  31. package/assets/payload/.agents/schemas/artifact-scope.json +237 -0
  32. package/assets/payload/.agents/schemas/artifacts/context.schema.json +11 -0
  33. package/assets/payload/.agents/schemas/artifacts/decision_log.schema.json +12 -0
  34. package/assets/payload/.agents/schemas/artifacts/evidence.schema.json +17 -0
  35. package/assets/payload/.agents/schemas/artifacts/plan.schema.json +83 -0
  36. package/assets/payload/.agents/schemas/artifacts/spec.schema.json +101 -0
  37. package/assets/payload/.agents/schemas/artifacts/task.schema.json +121 -0
  38. package/assets/payload/.agents/schemas/artifacts/update.schema.json +12 -0
  39. package/assets/payload/.agents/schemas/artifacts/workstream.schema.json +66 -0
  40. package/assets/payload/.agents/schemas/evidence-map.json +53 -0
  41. package/assets/payload/.agents/schemas/learning/closeout-learning-proposal.schema.json +20 -0
  42. package/assets/payload/.agents/schemas/learning/delivery-metric-event.schema.json +21 -0
  43. package/assets/payload/.agents/schemas/leases/lease.schema.json +39 -0
  44. package/assets/payload/.agents/schemas/metrics/delivery-event.schema.json +29 -0
  45. package/assets/payload/.agents/schemas/metrics/delivery-events.schema.json +49 -0
  46. package/assets/payload/.agents/schemas/operating-modes.json +42 -0
  47. package/assets/payload/.agents/schemas/status-transitions.json +31 -0
  48. package/assets/payload/.agents/schemas/sync/drift-report.schema.json +25 -0
  49. package/assets/payload/.agents/schemas/sync/drift-taxonomy.json +38 -0
  50. package/assets/payload/.agents/schemas/sync/sync-map.schema.json +39 -0
  51. package/assets/payload/.agents/scripts/README.md +1 -0
  52. package/assets/payload/.agents/scripts/audit-context-files.mjs +54 -0
  53. package/assets/payload/.agents/scripts/audit-context-scoring.mjs +14 -0
  54. package/assets/payload/.agents/scripts/build-drift-report.mjs +133 -0
  55. package/assets/payload/.agents/scripts/check-artifact-schemas.mjs +116 -0
  56. package/assets/payload/.agents/scripts/check-closeout-learning-proposals.mjs +23 -0
  57. package/assets/payload/.agents/scripts/check-context-audit.mjs +61 -0
  58. package/assets/payload/.agents/scripts/check-delivery-metric-events.mjs +35 -0
  59. package/assets/payload/.agents/scripts/check-delivery-metrics.mjs +52 -0
  60. package/assets/payload/.agents/scripts/check-evidence-map.mjs +143 -0
  61. package/assets/payload/.agents/scripts/check-github-status-inspection.mjs +93 -0
  62. package/assets/payload/.agents/scripts/check-github-sync.mjs +159 -0
  63. package/assets/payload/.agents/scripts/check-handoff-summaries.mjs +57 -0
  64. package/assets/payload/.agents/scripts/check-lease-conflicts.mjs +24 -0
  65. package/assets/payload/.agents/scripts/check-lease-contracts.mjs +17 -0
  66. package/assets/payload/.agents/scripts/check-linear-issue-inspection.mjs +63 -0
  67. package/assets/payload/.agents/scripts/check-local-sync-map.mjs +151 -0
  68. package/assets/payload/.agents/scripts/check-log-safety.sh +62 -0
  69. package/assets/payload/.agents/scripts/check-operating-modes.mjs +99 -0
  70. package/assets/payload/.agents/scripts/check-path-standards.sh +1 -1
  71. package/assets/payload/.agents/scripts/check-skill-output-evals.mjs +13 -0
  72. package/assets/payload/.agents/scripts/check-status-transitions.mjs +169 -0
  73. package/assets/payload/.agents/scripts/check-strict-fixtures.mjs +140 -0
  74. package/assets/payload/.agents/scripts/check-sync-schemas.mjs +52 -0
  75. package/assets/payload/.agents/scripts/check-text-safety.mjs +158 -0
  76. package/assets/payload/.agents/scripts/check-worktree-health.mjs +100 -0
  77. package/assets/payload/.agents/scripts/fix-path-standards.sh +1 -1
  78. package/assets/payload/.agents/scripts/git-sparse-download.sh +0 -0
  79. package/assets/payload/.agents/scripts/inspect-github-sync.mjs +108 -0
  80. package/assets/payload/.agents/scripts/lease-manager.mjs +88 -0
  81. package/assets/payload/.agents/scripts/log-event.js +3 -0
  82. package/assets/payload/.agents/scripts/log-event.sh +0 -0
  83. package/assets/payload/.agents/scripts/plan-sync-repairs.mjs +66 -0
  84. package/assets/payload/.agents/scripts/pm/blocked.sh +0 -0
  85. package/assets/payload/.agents/scripts/pm/epic-list.sh +0 -0
  86. package/assets/payload/.agents/scripts/pm/in-progress.sh +0 -0
  87. package/assets/payload/.agents/scripts/pm/init.sh +0 -0
  88. package/assets/payload/.agents/scripts/pm/next.sh +0 -0
  89. package/assets/payload/.agents/scripts/pm/prd-list.sh +0 -0
  90. package/assets/payload/.agents/scripts/pm/search.sh +0 -0
  91. package/assets/payload/.agents/scripts/pm/standup.sh +0 -0
  92. package/assets/payload/.agents/scripts/pm/status.sh +0 -0
  93. package/assets/payload/.agents/scripts/pm/validate.sh +657 -2
  94. package/assets/payload/.agents/scripts/propose-closeout-learning.mjs +20 -0
  95. package/assets/payload/.agents/scripts/query-log.sh +0 -0
  96. package/assets/payload/.agents/scripts/read-local-sync-map.mjs +135 -0
  97. package/assets/payload/.agents/scripts/select-next-task.mjs +22 -0
  98. package/assets/payload/.agents/scripts/summarize-project-metrics.mjs +15 -0
  99. package/assets/payload/.agents/scripts/test-and-log.sh +0 -0
  100. package/assets/payload/.agents/skills/README.md +6 -0
  101. package/assets/payload/.agents/skills/closeout-skill/SKILL.md +3 -0
  102. package/assets/payload/.agents/skills/closeout-skill/references/runbook.md +5 -2
  103. package/assets/payload/.agents/skills/closeout-skill/templates/closure-checklist.md +2 -0
  104. package/assets/payload/.agents/skills/closeout-skill/templates/learning-proposal.md +21 -0
  105. package/assets/payload/.agents/skills/closeout-skill/templates/learning-proposals.md +25 -0
  106. package/assets/payload/.agents/skills/manage-context/SKILL.md +55 -0
  107. package/assets/payload/.agents/skills/manage-context/references/context-audit-checklist.md +26 -0
  108. package/assets/payload/.agents/skills/manage-context/references/runbook.md +26 -0
  109. package/assets/payload/.agents/skills/manage-context/templates/context-debt-report.md +22 -0
  110. package/assets/payload/.agents/skills/manage-context/templates/context-refresh-summary.md +13 -0
  111. package/assets/payload/.agents/skills/onboarding/SKILL.md +49 -0
  112. package/assets/payload/.agents/skills/onboarding/references/agents-md-best-practices.md +76 -0
  113. package/assets/payload/.agents/skills/prototype-skill/SKILL.md +51 -0
  114. package/assets/payload/.agents/skills/prototype-skill/references/probe-design-checklist.md +26 -0
  115. package/assets/payload/.agents/skills/prototype-skill/references/runbook.md +27 -0
  116. package/assets/payload/.agents/skills/prototype-skill/templates/probe-approval-recommendation.md +13 -0
  117. package/assets/payload/.agents/skills/prototype-skill/templates/probe-findings.md +16 -0
  118. package/assets/payload/.agents/validation-fixtures/strict/invalid/broken-dependencies/dependency.md +18 -0
  119. package/assets/payload/.agents/validation-fixtures/strict/invalid/broken-dependencies/task.md +24 -0
  120. package/assets/payload/.agents/validation-fixtures/strict/invalid/invalid-transition/task.md +20 -0
  121. package/assets/payload/.agents/validation-fixtures/strict/invalid/missing-evidence/task.md +27 -0
  122. package/assets/payload/.agents/validation-fixtures/strict/invalid/path-leak/task.md +27 -0
  123. package/assets/payload/.agents/validation-fixtures/strict/invalid/stale-context/context.md +9 -0
  124. package/assets/payload/.agents/validation-fixtures/strict/manifest.json +11 -0
  125. package/assets/payload/.agents/validation-fixtures/strict/valid/minimal-project/task.md +27 -0
  126. package/assets/payload/.delano/viewer/README.md +19 -0
  127. package/assets/payload/.delano/viewer/public/app.js +818 -0
  128. package/assets/payload/.delano/viewer/public/explorer.svg +3 -0
  129. package/assets/payload/.delano/viewer/public/index.html +21 -0
  130. package/assets/payload/.delano/viewer/public/markdown.svg +6 -0
  131. package/assets/payload/.delano/viewer/public/styles.css +1042 -0
  132. package/assets/payload/.delano/viewer/public/vscode.svg +24 -0
  133. package/assets/payload/.delano/viewer/server.js +389 -0
  134. package/assets/payload/.project/templates/plan.md +1 -1
  135. package/assets/payload/.project/templates/spec.md +1 -1
  136. package/assets/payload/.project/templates/task.md +1 -0
  137. package/assets/payload/HANDBOOK.md +66 -45
  138. package/assets/payload/install-delano.sh +0 -0
  139. package/install-delano.sh +0 -0
  140. package/package.json +31 -2
  141. package/src/cli/commands/onboarding.js +29 -0
  142. package/src/cli/commands/viewer.js +81 -0
  143. package/src/cli/index.js +20 -0
  144. package/src/cli/lib/install.js +1 -0
  145. package/src/cli/lib/onboarding.js +243 -0
@@ -0,0 +1,151 @@
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ const repoRoot = resolveRepoRoot(__dirname);
8
+ const errors = [];
9
+ const warnings = [];
10
+ const jsonMode = process.argv.includes("--json");
11
+
12
+ const syncMap = buildLocalSyncMap(repoRoot);
13
+ validateSyncMap(syncMap);
14
+
15
+ if (jsonMode) {
16
+ console.log(JSON.stringify({ sync_map: syncMap, errors, warnings }, null, 2));
17
+ } else if (errors.length === 0) {
18
+ const taskCount = syncMap.projects.reduce((sum, project) => sum + project.tasks.length, 0);
19
+ console.log(`Local sync map check passed for ${syncMap.projects.length} projects and ${taskCount} tasks.`);
20
+ if (warnings.length > 0) for (const warning of warnings) console.warn(`Warning: ${warning}`);
21
+ }
22
+
23
+ if (errors.length > 0) {
24
+ if (!jsonMode) {
25
+ console.error("Local sync map check failed:");
26
+ for (const error of errors) console.error(`- ${error}`);
27
+ }
28
+ process.exit(1);
29
+ }
30
+
31
+ function buildLocalSyncMap(root) {
32
+ const projectsRoot = path.join(root, ".project", "projects");
33
+ const linearMap = readJson(path.join(root, ".project", "registry", "linear-map.json"), "linear map", { projects: {}, tasks: {} });
34
+ const projects = [];
35
+
36
+ if (!existsSync(projectsRoot)) {
37
+ errors.push("Missing .project/projects directory.");
38
+ return { schema_version: 1, source: "local-project-contracts", projects };
39
+ }
40
+
41
+ for (const entry of readdirSync(projectsRoot, { withFileTypes: true })) {
42
+ if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
43
+ const projectDir = path.join(projectsRoot, entry.name);
44
+ const spec = parseFrontmatter(readText(path.join(projectDir, "spec.md")));
45
+ const plan = parseFrontmatter(readText(path.join(projectDir, "plan.md")));
46
+ const projectSlug = spec.slug || entry.name;
47
+ const registryProject = linearMap.projects?.[projectSlug] || {};
48
+ const project = {
49
+ slug: projectSlug,
50
+ local_path: `.project/projects/${entry.name}`,
51
+ linear_project_id: firstNonEmpty(plan.linear_project_id, spec.linear_project_id, registryProject.linear_project_id, registryProject.id),
52
+ github_repo: firstNonEmpty(plan.github_repo, spec.github_repo, registryProject.github_repo),
53
+ tasks: []
54
+ };
55
+
56
+ const tasksDir = path.join(projectDir, "tasks");
57
+ if (existsSync(tasksDir)) {
58
+ for (const taskEntry of readdirSync(tasksDir, { withFileTypes: true })) {
59
+ if (!taskEntry.isFile() || !taskEntry.name.endsWith(".md")) continue;
60
+ const taskPath = path.join(tasksDir, taskEntry.name);
61
+ const fm = parseFrontmatter(readText(taskPath));
62
+ if (!fm.id) {
63
+ warnings.push(`${project.local_path}/tasks/${taskEntry.name} has no task id and was skipped.`);
64
+ continue;
65
+ }
66
+ const registryTask = linearMap.tasks?.[`${projectSlug}:${fm.id}`] || linearMap.tasks?.[fm.id] || {};
67
+ project.tasks.push({
68
+ local_id: fm.id,
69
+ local_path: `${project.local_path}/tasks/${taskEntry.name}`,
70
+ status: fm.status || "unknown",
71
+ linear_issue_id: firstNonEmpty(fm.linear_issue_id, registryTask.linear_issue_id, registryTask.id),
72
+ github_issue: firstNonEmpty(fm.github_issue, registryTask.github_issue),
73
+ github_pr: firstNonEmpty(fm.github_pr, registryTask.github_pr),
74
+ depends_on: parseList(fm.depends_on || "[]")
75
+ });
76
+ }
77
+ }
78
+ project.tasks.sort((a, b) => a.local_id.localeCompare(b.local_id));
79
+ projects.push(project);
80
+ }
81
+
82
+ projects.sort((a, b) => a.slug.localeCompare(b.slug));
83
+ return { schema_version: 1, source: "local-project-contracts", projects };
84
+ }
85
+
86
+ function validateSyncMap(syncMap) {
87
+ if (syncMap.schema_version !== 1) errors.push("local sync map schema_version must be 1.");
88
+ if (!Array.isArray(syncMap.projects)) errors.push("local sync map projects must be an array.");
89
+ const seenProjects = new Set();
90
+ for (const project of syncMap.projects || []) {
91
+ if (!project.slug || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(project.slug)) errors.push(`invalid project slug: ${project.slug || "<missing>"}`);
92
+ if (seenProjects.has(project.slug)) errors.push(`duplicate project slug: ${project.slug}`);
93
+ seenProjects.add(project.slug);
94
+ if (!project.local_path || !/^\.project\/projects\/[^/]+$/.test(project.local_path)) errors.push(`invalid local project path for ${project.slug}`);
95
+ const seenTasks = new Set();
96
+ for (const task of project.tasks || []) {
97
+ const taskKey = `${project.slug}:${task.local_id}`;
98
+ if (!/^T-[0-9]{3}$/.test(task.local_id || "")) errors.push(`invalid task id in ${project.slug}: ${task.local_id || "<missing>"}`);
99
+ if (seenTasks.has(task.local_id)) errors.push(`duplicate task id in ${project.slug}: ${task.local_id}`);
100
+ seenTasks.add(task.local_id);
101
+ if (!task.local_path?.startsWith(`${project.local_path}/tasks/`)) errors.push(`invalid task path for ${taskKey}`);
102
+ if (!Array.isArray(task.depends_on)) errors.push(`depends_on must be normalized as an array for ${taskKey}`);
103
+ }
104
+ }
105
+ }
106
+
107
+ function parseFrontmatter(text) {
108
+ const match = text.match(/^---\n([\s\S]*?)\n---\n/);
109
+ if (!match) return {};
110
+ const result = {};
111
+ for (const line of match[1].split("\n")) {
112
+ const index = line.indexOf(":");
113
+ if (index === -1) continue;
114
+ result[line.slice(0, index).trim()] = line.slice(index + 1).trim();
115
+ }
116
+ return result;
117
+ }
118
+
119
+ function parseList(raw) {
120
+ const value = String(raw).trim();
121
+ if (!value || value === "[]") return [];
122
+ if (value.startsWith("[") && value.endsWith("]")) {
123
+ return value.slice(1, -1).split(",").map((item) => item.trim().replace(/^[\"']|[\"']$/g, "")).filter(Boolean);
124
+ }
125
+ return [value.replace(/^[\"']|[\"']$/g, "")].filter(Boolean);
126
+ }
127
+
128
+ function firstNonEmpty(...values) {
129
+ for (const value of values) {
130
+ if (typeof value === "string" && value.trim() !== "") return value.trim();
131
+ }
132
+ return "";
133
+ }
134
+
135
+ function readText(filePath) {
136
+ try { return readFileSync(filePath, "utf8"); }
137
+ catch { return ""; }
138
+ }
139
+
140
+ function readJson(filePath, label, fallback) {
141
+ try { return JSON.parse(readFileSync(filePath, "utf8")); }
142
+ catch (error) { warnings.push(`Could not read ${label}: ${error.message}`); return fallback; }
143
+ }
144
+
145
+ function resolveRepoRoot(startDir) {
146
+ const candidates = [path.resolve(startDir, ".."), path.resolve(startDir, "..", "..")];
147
+ for (const candidate of candidates) {
148
+ if (existsSync(path.join(candidate, ".agents")) && existsSync(path.join(candidate, ".project"))) return candidate;
149
+ }
150
+ return path.resolve(startDir, "..");
151
+ }
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
5
+ cd "$root"
6
+
7
+ errors=0
8
+
9
+ require_file() {
10
+ local file="$1"
11
+ if [[ -f "$file" ]]; then
12
+ echo "✅ $file"
13
+ else
14
+ echo "❌ Missing log-safety file: $file"
15
+ errors=$((errors + 1))
16
+ fi
17
+ }
18
+
19
+ echo "Log safety check"
20
+ echo "================"
21
+
22
+ require_file ".agents/common/log-safety.js"
23
+ require_file ".agents/hooks/user-prompt-logger.js"
24
+ require_file ".agents/logs/schema.md"
25
+
26
+ if [[ -e .claude || -L .claude ]]; then
27
+ require_file ".claude/common/log-safety.js"
28
+ fi
29
+
30
+ if grep -q 'prompt_hash' .agents/hooks/user-prompt-logger.js && grep -q 'DELANO_LOG_RAW_PROMPTS' .agents/hooks/user-prompt-logger.js; then
31
+ echo "✅ Prompt logger stores hash metadata by default and gates raw text"
32
+ else
33
+ echo "❌ Prompt logger must store prompt_hash and gate raw text behind DELANO_LOG_RAW_PROMPTS"
34
+ errors=$((errors + 1))
35
+ fi
36
+
37
+ if grep -q 'redactObject' .agents/hooks/post-tool-logger.js && grep -q 'redactObject' .agents/scripts/log-event.js; then
38
+ echo "✅ Change/event metadata passes through redaction helpers"
39
+ else
40
+ echo "❌ Change/event logging must redact metadata before write"
41
+ errors=$((errors + 1))
42
+ fi
43
+
44
+ raw_schema_matches="$(grep -RIn '"prompt"[[:space:]]*:[[:space:]]*"string"' .agents .claude 2>/dev/null || true)"
45
+ if [[ -n "$raw_schema_matches" ]]; then
46
+ echo "❌ Raw prompt schema still documented:"
47
+ echo "$raw_schema_matches"
48
+ errors=$((errors + 1))
49
+ else
50
+ echo "✅ Raw prompt schema is not documented as default"
51
+ fi
52
+
53
+ if grep -q 'echo .*\$root' .agents/hooks/bash-worktree-fix.sh; then
54
+ echo "❌ bash-worktree-fix.sh prints the absolute repository root"
55
+ errors=$((errors + 1))
56
+ else
57
+ echo "✅ bash-worktree-fix.sh prints a placeholder instead of the absolute root"
58
+ fi
59
+
60
+ if [[ $errors -gt 0 ]]; then
61
+ exit 1
62
+ fi
@@ -0,0 +1,99 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ const repoRoot = resolveRepoRoot(__dirname);
8
+ const modesPath = path.join(repoRoot, ".agents", "schemas", "operating-modes.json");
9
+ const rulePath = path.join(repoRoot, ".agents", "rules", "delivery-modes.md");
10
+ const errors = [];
11
+
12
+ const contract = readJson(modesPath, "operating modes contract");
13
+ const modes = Array.isArray(contract.modes) ? contract.modes : [];
14
+ const expectedModes = [0, 1, 2, 3, 4];
15
+ const expectedSlugs = ["patch", "scoped-change", "feature", "uncertain-feature", "multi-stream"];
16
+
17
+ if (contract.schema_version !== 1) {
18
+ errors.push("operating-modes.json schema_version must be 1.");
19
+ }
20
+
21
+ if (modes.length !== expectedModes.length) {
22
+ errors.push(`operating-modes.json must define ${expectedModes.length} modes.`);
23
+ }
24
+
25
+ const seenModes = new Set();
26
+ const seenSlugs = new Set();
27
+ for (const [index, expectedMode] of expectedModes.entries()) {
28
+ const mode = modes[index];
29
+ if (!mode) continue;
30
+
31
+ if (mode.mode !== expectedMode) {
32
+ errors.push(`mode index ${index} must be mode ${expectedMode}.`);
33
+ }
34
+ if (mode.slug !== expectedSlugs[index]) {
35
+ errors.push(`mode ${expectedMode} must use slug ${expectedSlugs[index]}.`);
36
+ }
37
+ if (seenModes.has(mode.mode)) {
38
+ errors.push(`duplicate operating mode: ${mode.mode}`);
39
+ }
40
+ seenModes.add(mode.mode);
41
+ if (seenSlugs.has(mode.slug)) {
42
+ errors.push(`duplicate operating mode slug: ${mode.slug}`);
43
+ }
44
+ seenSlugs.add(mode.slug);
45
+
46
+ for (const field of ["name", "use_when"]) {
47
+ if (typeof mode[field] !== "string" || mode[field].trim() === "") {
48
+ errors.push(`mode ${expectedMode} must define non-empty ${field}.`);
49
+ }
50
+ }
51
+ if (!Array.isArray(mode.requires) || mode.requires.length === 0) {
52
+ errors.push(`mode ${expectedMode} must define at least one requirement.`);
53
+ }
54
+ }
55
+
56
+ const doc = readText(rulePath, "delivery modes rule");
57
+ for (const slug of expectedSlugs) {
58
+ if (!doc.includes(slug)) {
59
+ errors.push(`delivery-modes.md must document slug: ${slug}`);
60
+ }
61
+ }
62
+
63
+ if (errors.length > 0) {
64
+ console.error("Operating modes check failed:");
65
+ for (const error of errors) console.error(`- ${error}`);
66
+ process.exit(1);
67
+ }
68
+
69
+ console.log("Operating modes check passed for modes 0 through 4.");
70
+
71
+ function readJson(filePath, label) {
72
+ try {
73
+ return JSON.parse(readFileSync(filePath, "utf8"));
74
+ } catch (error) {
75
+ errors.push(`Could not read ${label} at ${toRepoPath(filePath)}: ${error.message}`);
76
+ return {};
77
+ }
78
+ }
79
+
80
+ function readText(filePath, label) {
81
+ try {
82
+ return readFileSync(filePath, "utf8");
83
+ } catch (error) {
84
+ errors.push(`Could not read ${label} at ${toRepoPath(filePath)}: ${error.message}`);
85
+ return "";
86
+ }
87
+ }
88
+
89
+ function resolveRepoRoot(startDir) {
90
+ const candidates = [path.resolve(startDir, ".."), path.resolve(startDir, "..", "..")];
91
+ for (const candidate of candidates) {
92
+ if (existsSync(path.join(candidate, ".agents", "schemas"))) return candidate;
93
+ }
94
+ return path.resolve(startDir, "..");
95
+ }
96
+
97
+ function toRepoPath(filePath) {
98
+ return path.relative(repoRoot, filePath).split(path.sep).join("/");
99
+ }
@@ -17,7 +17,7 @@ if find .project .agents "${compat_paths[@]}" \
17
17
  \( -name '*.md' -o -name '*.json' -o -name '*.yaml' -o -name '*.yml' \) \
18
18
  -not -path '.agents/logs/*' \
19
19
  -not -path '.claude/logs/*' \
20
- -print0 | xargs -0 grep -nE '(/home/[^[:space:]]+|/Users/[^[:space:]]+|[A-Za-z]:\\[^[:space:]]+)' > "$matches_file" 2>/dev/null; then
20
+ -print0 | xargs -0 grep -nE '(/home/[^[:space:]]+|/Users/[^[:space:]]+|/mnt/[A-Za-z]/[^[:space:]]+|[A-Za-z]:\\[^[:space:]]+)' > "$matches_file" 2>/dev/null; then
21
21
  echo "Absolute path violations found:"
22
22
  cat "$matches_file"
23
23
  exit 1
@@ -0,0 +1,13 @@
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename);
5
+ const repoRoot = resolveRepoRoot(__dirname);
6
+ const root = path.join(repoRoot, ".agents", "eval-fixtures", "skill-output");
7
+ const errors=[]; let checked=0;
8
+ for (const kind of ["valid","invalid"]) for (const dir of listDirs(path.join(root, kind))) { const fixture=JSON.parse(readFileSync(path.join(root, kind, dir, "output.json"), "utf8")); checked++; const valid=Array.isArray(fixture.evidence) && fixture.evidence.length > 0 && fixture.privacy === "metadata-only"; if (kind === "valid" && !valid) errors.push(`${dir} expected valid skill output`); if (kind === "invalid" && valid) errors.push(`${dir} expected invalid skill output`); }
9
+ if (checked < 2) errors.push("expected at least one valid and one invalid skill output fixture");
10
+ if(errors.length){ console.error("Skill output eval check failed:"); errors.forEach(e=>console.error(`- ${e}`)); process.exit(1); }
11
+ console.log(`Skill output eval check passed for ${checked} fixture(s).`);
12
+ function listDirs(dir){ if(!existsSync(dir)) return []; return readdirSync(dir,{withFileTypes:true}).filter(d=>d.isDirectory()).map(d=>d.name); }
13
+ function resolveRepoRoot(startDir){ for(const c of [path.resolve(startDir,".."),path.resolve(startDir,"..","..")]) if(existsSync(path.join(c,".agents"))) return c; return path.resolve(startDir,".."); }
@@ -0,0 +1,169 @@
1
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ const repoRoot = resolveRepoRoot(__dirname);
8
+ const contractPath = path.join(repoRoot, ".agents", "schemas", "status-transitions.json");
9
+ const args = process.argv.slice(2);
10
+ const projectsRoot = path.resolve(repoRoot, valueAfter(args, "--projects-root") || path.join(".project", "projects"));
11
+ const errors = [];
12
+
13
+ const contract = readJson(contractPath, "status transition contract");
14
+ if (contract.schema_version !== 1) {
15
+ errors.push("status-transitions.json schema_version must be 1.");
16
+ }
17
+ const rules = Array.isArray(contract.task_rules) ? contract.task_rules : [];
18
+ for (const requiredRule of ["ready-dependencies-done", "blocked-owner-check-back"]) {
19
+ if (!rules.some((rule) => rule.id === requiredRule)) {
20
+ errors.push(`status transition contract missing rule: ${requiredRule}`);
21
+ }
22
+ }
23
+
24
+ const transitionRequest = parseTransitionArgs(args);
25
+ if (transitionRequest) {
26
+ validateTransitionRequest(transitionRequest);
27
+ finish();
28
+ }
29
+
30
+ for (const projectDir of listDirectories(projectsRoot)) {
31
+ const tasksDir = path.join(projectDir, "tasks");
32
+ if (!existsSync(tasksDir)) continue;
33
+
34
+ const tasks = new Map();
35
+ for (const taskFile of listMarkdownFiles(tasksDir)) {
36
+ const frontmatter = parseFrontmatter(taskFile);
37
+ const id = frontmatter.id || path.basename(taskFile, ".md").split("-").slice(0, 2).join("-");
38
+ tasks.set(id, { file: taskFile, frontmatter });
39
+ }
40
+
41
+ for (const [taskId, task] of tasks.entries()) {
42
+ const status = task.frontmatter.status || "";
43
+ const dependencies = parseList(task.frontmatter.depends_on || "[]");
44
+
45
+ if (["ready", "in-progress", "done"].includes(status)) {
46
+ for (const dependencyId of dependencies) {
47
+ const dependency = tasks.get(dependencyId);
48
+ if (!dependency) continue;
49
+ const dependencyStatus = dependency.frontmatter.status || "";
50
+ if (dependencyStatus !== "done") {
51
+ const message = `${toRepoPath(task.file)} has status ${status} but depends on unresolved ${dependencyId} (${dependencyStatus || "missing status"}).`;
52
+ errors.push(message);
53
+ }
54
+ }
55
+ }
56
+
57
+ if (status === "blocked") {
58
+ for (const field of ["blocked_owner", "blocked_check_back"]) {
59
+ if (!task.frontmatter[field] || task.frontmatter[field].trim() === "") {
60
+ errors.push(`${toRepoPath(task.file)} is blocked but missing ${field}.`);
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }
66
+
67
+ finish();
68
+
69
+ function parseTransitionArgs(args) {
70
+ if (!args.includes("--validate-transition")) return null;
71
+ const nextStatus = valueAfter(args, "--validate-transition");
72
+ const dependencyStatuses = valueAfter(args, "--dependency-statuses")
73
+ .split(",")
74
+ .map((value) => value.trim())
75
+ .filter(Boolean);
76
+ const blockedOwner = valueAfter(args, "--blocked-owner");
77
+ const blockedCheckBack = valueAfter(args, "--blocked-check-back");
78
+ return { nextStatus, dependencyStatuses, blockedOwner, blockedCheckBack };
79
+ }
80
+
81
+ function validateTransitionRequest(request) {
82
+ if (["ready", "in-progress", "done"].includes(request.nextStatus)) {
83
+ for (const dependencyStatus of request.dependencyStatuses) {
84
+ if (dependencyStatus !== "done") {
85
+ errors.push(`cannot transition to ${request.nextStatus} with unresolved dependency status: ${dependencyStatus}`);
86
+ }
87
+ }
88
+ }
89
+
90
+ if (request.nextStatus === "blocked") {
91
+ if (!request.blockedOwner) errors.push("cannot transition to blocked without blocked_owner");
92
+ if (!request.blockedCheckBack) errors.push("cannot transition to blocked without blocked_check_back");
93
+ }
94
+ }
95
+
96
+ function valueAfter(args, flag) {
97
+ const index = args.indexOf(flag);
98
+ if (index === -1 || index === args.length - 1) return "";
99
+ return args[index + 1];
100
+ }
101
+
102
+ function finish() {
103
+ if (errors.length > 0) {
104
+ console.error("Status transition check failed:");
105
+ for (const error of errors) console.error(`- ${error}`);
106
+ process.exit(1);
107
+ }
108
+
109
+ console.log("Status transition check passed for current project tasks.");
110
+ process.exit(0);
111
+ }
112
+
113
+ function readJson(filePath, label) {
114
+ try { return JSON.parse(readFileSync(filePath, "utf8")); }
115
+ catch (error) { errors.push(`Could not read ${label} at ${toRepoPath(filePath)}: ${error.message}`); return {}; }
116
+ }
117
+
118
+ function parseFrontmatter(filePath) {
119
+ const text = readFileSync(filePath, "utf8");
120
+ const match = text.match(/^---\n([\s\S]*?)\n---\n/);
121
+ if (!match) {
122
+ errors.push(`${toRepoPath(filePath)} is missing frontmatter.`);
123
+ return {};
124
+ }
125
+ const result = {};
126
+ for (const line of match[1].split("\n")) {
127
+ const index = line.indexOf(":");
128
+ if (index === -1) continue;
129
+ result[line.slice(0, index).trim()] = line.slice(index + 1).trim();
130
+ }
131
+ return result;
132
+ }
133
+
134
+ function parseList(raw) {
135
+ const value = raw.trim();
136
+ if (!value || value === "[]") return [];
137
+ if (value.startsWith("[") && value.endsWith("]")) {
138
+ const inner = value.slice(1, -1).trim();
139
+ if (!inner) return [];
140
+ return inner.split(",").map((item) => item.trim().replace(/^['\"]|['\"]$/g, "")).filter(Boolean);
141
+ }
142
+ return [value.replace(/^['\"]|['\"]$/g, "")].filter(Boolean);
143
+ }
144
+
145
+ function listDirectories(root) {
146
+ if (!existsSync(root)) return [];
147
+ return readdirSync(root, { withFileTypes: true })
148
+ .filter((entry) => entry.isDirectory())
149
+ .map((entry) => path.join(root, entry.name));
150
+ }
151
+
152
+ function listMarkdownFiles(root) {
153
+ if (!existsSync(root)) return [];
154
+ return readdirSync(root, { withFileTypes: true })
155
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
156
+ .map((entry) => path.join(root, entry.name));
157
+ }
158
+
159
+ function resolveRepoRoot(startDir) {
160
+ const candidates = [path.resolve(startDir, ".."), path.resolve(startDir, "..", "..")];
161
+ for (const candidate of candidates) {
162
+ if (existsSync(path.join(candidate, ".project", "projects")) && existsSync(path.join(candidate, ".agents"))) return candidate;
163
+ }
164
+ return path.resolve(startDir, "..");
165
+ }
166
+
167
+ function toRepoPath(filePath) {
168
+ return path.relative(repoRoot, filePath).split(path.sep).join("/");
169
+ }
@@ -0,0 +1,140 @@
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ const repoRoot = resolveRepoRoot(__dirname);
8
+ const fixturesRoot = path.join(repoRoot, ".agents", "validation-fixtures", "strict");
9
+ const manifestPath = path.join(fixturesRoot, "manifest.json");
10
+ const errors = [];
11
+
12
+ const manifest = readJson(manifestPath, "strict fixture manifest");
13
+ const fixtures = Array.isArray(manifest.fixtures) ? manifest.fixtures : [];
14
+ const requiredInvalidRules = new Set(["missing-evidence", "broken-dependencies", "stale-context", "path-leak", "invalid-transition"]);
15
+
16
+ if (manifest.schema_version !== 1) errors.push("strict fixture manifest schema_version must be 1.");
17
+ if (!fixtures.some((fixture) => fixture.kind === "valid" && fixture.expected === "pass")) {
18
+ errors.push("strict fixtures must include at least one valid passing project.");
19
+ }
20
+
21
+ for (const rule of requiredInvalidRules) {
22
+ if (!fixtures.some((fixture) => fixture.kind === "invalid" && fixture.expected_rule === rule)) {
23
+ errors.push(`strict fixtures missing invalid fixture for rule: ${rule}`);
24
+ }
25
+ }
26
+
27
+ for (const fixture of fixtures) {
28
+ const fixturePath = path.join(fixturesRoot, fixture.path || "");
29
+ if (!existsSync(fixturePath)) {
30
+ errors.push(`fixture path does not exist: ${fixture.path}`);
31
+ continue;
32
+ }
33
+
34
+ const violations = validateFixture(fixturePath);
35
+ if (fixture.kind === "valid" && violations.length > 0) {
36
+ errors.push(`${fixture.name} expected pass but produced violations: ${violations.join(", ")}`);
37
+ }
38
+ if (fixture.kind === "invalid" && !violations.includes(fixture.expected_rule)) {
39
+ errors.push(`${fixture.name} expected rule ${fixture.expected_rule} but produced: ${violations.join(", ") || "none"}`);
40
+ }
41
+ }
42
+
43
+ if (errors.length > 0) {
44
+ console.error("Strict fixture check failed:");
45
+ for (const error of errors) console.error(`- ${error}`);
46
+ process.exit(1);
47
+ }
48
+
49
+ console.log(`Strict fixture check passed for ${fixtures.length} fixtures.`);
50
+
51
+ function validateFixture(fixturePath) {
52
+ const violations = new Set();
53
+ const markdownFiles = listMarkdownFiles(fixturePath);
54
+ const tasks = new Map();
55
+
56
+ for (const file of markdownFiles) {
57
+ const text = readFileSync(file, "utf8");
58
+ if (/PATH_LEAK_TOKEN\(/.test(text) || /\/home\/[^\s)]+/.test(text) || /\/Users\/[^\s)]+/.test(text) || /\/mnt\/[A-Za-z]\/[^\s)]+/.test(text) || /[A-Z]:\\Users\\[^\s)]+/i.test(text)) violations.add("path-leak");
59
+ const frontmatter = parseFrontmatter(text);
60
+ if (frontmatter.id && frontmatter.status) tasks.set(frontmatter.id, { file, text, frontmatter });
61
+ if (frontmatter.review_by && Date.parse(frontmatter.review_by) < Date.parse("2026-04-29T00:00:00Z")) violations.add("stale-context");
62
+ }
63
+
64
+ for (const task of tasks.values()) {
65
+ const fm = task.frontmatter;
66
+ const dependencies = parseList(fm.depends_on || "[]");
67
+ if (["ready", "in-progress", "done"].includes(fm.status)) {
68
+ for (const dependencyId of dependencies) {
69
+ const dependency = tasks.get(dependencyId);
70
+ if (dependency && dependency.frontmatter.status !== "done") violations.add("broken-dependencies");
71
+ }
72
+ }
73
+ if (fm.status === "blocked" && (!fm.blocked_owner || !fm.blocked_check_back)) violations.add("invalid-transition");
74
+ if (fm.status === "done") {
75
+ const acceptance = section(task.text, "Acceptance Criteria");
76
+ const evidence = section(task.text, "Evidence Log");
77
+ if (acceptance.includes("- [ ]") || !/^- \d{4}-\d{2}-\d{2}.*(validation passed|Validation:|passed:)/im.test(evidence)) {
78
+ violations.add("missing-evidence");
79
+ }
80
+ }
81
+ }
82
+
83
+ return [...violations].sort();
84
+ }
85
+
86
+ function parseFrontmatter(text) {
87
+ const match = text.match(/^---\n([\s\S]*?)\n---\n/);
88
+ if (!match) return {};
89
+ const result = {};
90
+ for (const line of match[1].split("\n")) {
91
+ const index = line.indexOf(":");
92
+ if (index === -1) continue;
93
+ result[line.slice(0, index).trim()] = line.slice(index + 1).trim();
94
+ }
95
+ return result;
96
+ }
97
+
98
+ function parseList(raw) {
99
+ const value = raw.trim();
100
+ if (!value || value === "[]") return [];
101
+ if (value.startsWith("[") && value.endsWith("]")) {
102
+ return value.slice(1, -1).split(",").map((item) => item.trim().replace(/^['\"]|['\"]$/g, "")).filter(Boolean);
103
+ }
104
+ return [value.replace(/^['\"]|['\"]$/g, "")].filter(Boolean);
105
+ }
106
+
107
+ function section(text, heading) {
108
+ const lines = text.split("\n");
109
+ const start = lines.findIndex((line) => line.trim() === `## ${heading}`);
110
+ if (start === -1) return "";
111
+ const collected = [];
112
+ for (const line of lines.slice(start + 1)) {
113
+ if (line.startsWith("## ")) break;
114
+ collected.push(line);
115
+ }
116
+ return collected.join("\n").trim();
117
+ }
118
+
119
+ function listMarkdownFiles(root) {
120
+ const files = [];
121
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
122
+ const fullPath = path.join(root, entry.name);
123
+ if (entry.isDirectory()) files.push(...listMarkdownFiles(fullPath));
124
+ if (entry.isFile() && entry.name.endsWith(".md")) files.push(fullPath);
125
+ }
126
+ return files;
127
+ }
128
+
129
+ function readJson(filePath, label) {
130
+ try { return JSON.parse(readFileSync(filePath, "utf8")); }
131
+ catch (error) { errors.push(`Could not read ${label}: ${error.message}`); return {}; }
132
+ }
133
+
134
+ function resolveRepoRoot(startDir) {
135
+ const candidates = [path.resolve(startDir, ".."), path.resolve(startDir, "..", "..")];
136
+ for (const candidate of candidates) {
137
+ if (existsSync(path.join(candidate, ".agents")) && existsSync(path.join(candidate, ".project"))) return candidate;
138
+ }
139
+ return path.resolve(startDir, "..");
140
+ }