javi-forge 1.5.0 → 1.6.1

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 (217) hide show
  1. package/README.md +191 -3
  2. package/ci-local/hooks/pre-push +17 -13
  3. package/dist/commands/analyze.d.ts +1 -1
  4. package/dist/commands/analyze.js +15 -15
  5. package/dist/commands/atlassian-mcp.d.ts +42 -0
  6. package/dist/commands/atlassian-mcp.js +98 -0
  7. package/dist/commands/ci.d.ts +3 -3
  8. package/dist/commands/ci.js +185 -147
  9. package/dist/commands/crash-recovery.d.ts +34 -0
  10. package/dist/commands/crash-recovery.js +123 -0
  11. package/dist/commands/doctor.d.ts +2 -2
  12. package/dist/commands/doctor.js +113 -61
  13. package/dist/commands/harness-audit.d.ts +35 -0
  14. package/dist/commands/harness-audit.js +277 -0
  15. package/dist/commands/init.d.ts +1 -1
  16. package/dist/commands/init.js +415 -118
  17. package/dist/commands/llmstxt.d.ts +1 -1
  18. package/dist/commands/llmstxt.js +36 -34
  19. package/dist/commands/parallel-batch.d.ts +42 -0
  20. package/dist/commands/parallel-batch.js +90 -0
  21. package/dist/commands/plugin.d.ts +26 -1
  22. package/dist/commands/plugin.js +138 -24
  23. package/dist/commands/secret-scanner.d.ts +30 -0
  24. package/dist/commands/secret-scanner.js +272 -0
  25. package/dist/commands/security-analysis.d.ts +74 -0
  26. package/dist/commands/security-analysis.js +487 -0
  27. package/dist/commands/security.d.ts +31 -0
  28. package/dist/commands/security.js +445 -0
  29. package/dist/commands/skill-scanner.d.ts +63 -0
  30. package/dist/commands/skill-scanner.js +383 -0
  31. package/dist/commands/skills.d.ts +139 -0
  32. package/dist/commands/skills.js +895 -0
  33. package/dist/commands/supply-chain.d.ts +23 -0
  34. package/dist/commands/supply-chain.js +126 -0
  35. package/dist/commands/tdd-pipeline.d.ts +17 -0
  36. package/dist/commands/tdd-pipeline.js +144 -0
  37. package/dist/commands/tdd.d.ts +21 -0
  38. package/dist/commands/tdd.js +120 -0
  39. package/dist/commands/team-presets.d.ts +53 -0
  40. package/dist/commands/team-presets.js +201 -0
  41. package/dist/commands/workflow.d.ts +23 -0
  42. package/dist/commands/workflow.js +114 -0
  43. package/dist/constants.d.ts +21 -0
  44. package/dist/constants.js +208 -37
  45. package/dist/index.js +400 -54
  46. package/dist/lib/agent-skills.d.ts +73 -0
  47. package/dist/lib/agent-skills.js +260 -0
  48. package/dist/lib/auto-skill-install.d.ts +37 -0
  49. package/dist/lib/auto-skill-install.js +92 -0
  50. package/dist/lib/auto-wire.d.ts +20 -0
  51. package/dist/lib/auto-wire.js +240 -0
  52. package/dist/lib/claudemd.d.ts +20 -0
  53. package/dist/lib/claudemd.js +222 -0
  54. package/dist/lib/codex-export.d.ts +16 -0
  55. package/dist/lib/codex-export.js +109 -0
  56. package/dist/lib/common.d.ts +1 -1
  57. package/dist/lib/common.js +52 -44
  58. package/dist/lib/context.d.ts +27 -0
  59. package/dist/lib/context.js +204 -0
  60. package/dist/lib/docker.d.ts +1 -1
  61. package/dist/lib/docker.js +141 -112
  62. package/dist/lib/frontmatter.d.ts +1 -1
  63. package/dist/lib/frontmatter.js +29 -15
  64. package/dist/lib/plugin.d.ts +19 -1
  65. package/dist/lib/plugin.js +174 -47
  66. package/dist/lib/skill-publish.d.ts +40 -0
  67. package/dist/lib/skill-publish.js +146 -0
  68. package/dist/lib/stack-detector.d.ts +38 -0
  69. package/dist/lib/stack-detector.js +207 -0
  70. package/dist/lib/template.d.ts +16 -1
  71. package/dist/lib/template.js +46 -17
  72. package/dist/lib/workflow/discovery.d.ts +19 -0
  73. package/dist/lib/workflow/discovery.js +68 -0
  74. package/dist/lib/workflow/index.d.ts +5 -0
  75. package/dist/lib/workflow/index.js +5 -0
  76. package/dist/lib/workflow/parser.d.ts +16 -0
  77. package/dist/lib/workflow/parser.js +198 -0
  78. package/dist/lib/workflow/renderer.d.ts +9 -0
  79. package/dist/lib/workflow/renderer.js +152 -0
  80. package/dist/lib/workflow/validator.d.ts +10 -0
  81. package/dist/lib/workflow/validator.js +189 -0
  82. package/dist/tasks/index.d.ts +4 -0
  83. package/dist/tasks/index.js +4 -0
  84. package/dist/tasks/scaffold-tasks.d.ts +3 -0
  85. package/dist/tasks/scaffold-tasks.js +14 -0
  86. package/dist/tasks/task-id.d.ts +30 -0
  87. package/dist/tasks/task-id.js +55 -0
  88. package/dist/tasks/task-tracker.d.ts +15 -0
  89. package/dist/tasks/task-tracker.js +81 -0
  90. package/dist/types/index.d.ts +252 -5
  91. package/dist/types/index.js +11 -1
  92. package/dist/ui/AnalyzeUI.d.ts +1 -1
  93. package/dist/ui/AnalyzeUI.js +38 -39
  94. package/dist/ui/App.d.ts +5 -3
  95. package/dist/ui/App.js +92 -46
  96. package/dist/ui/AutoSkills.d.ts +9 -0
  97. package/dist/ui/AutoSkills.js +124 -0
  98. package/dist/ui/CI.d.ts +2 -2
  99. package/dist/ui/CI.js +24 -26
  100. package/dist/ui/CIContext.d.ts +1 -1
  101. package/dist/ui/CIContext.js +3 -2
  102. package/dist/ui/CISelector.d.ts +2 -2
  103. package/dist/ui/CISelector.js +23 -15
  104. package/dist/ui/Doctor.d.ts +1 -1
  105. package/dist/ui/Doctor.js +35 -29
  106. package/dist/ui/Header.d.ts +1 -1
  107. package/dist/ui/Header.js +14 -14
  108. package/dist/ui/HookProfileSelector.d.ts +9 -0
  109. package/dist/ui/HookProfileSelector.js +54 -0
  110. package/dist/ui/LlmsTxt.d.ts +1 -1
  111. package/dist/ui/LlmsTxt.js +31 -22
  112. package/dist/ui/MemorySelector.d.ts +2 -2
  113. package/dist/ui/MemorySelector.js +28 -16
  114. package/dist/ui/NameInput.d.ts +1 -1
  115. package/dist/ui/NameInput.js +21 -21
  116. package/dist/ui/OptionSelector.d.ts +8 -2
  117. package/dist/ui/OptionSelector.js +83 -26
  118. package/dist/ui/Plugin.d.ts +4 -3
  119. package/dist/ui/Plugin.js +89 -29
  120. package/dist/ui/Progress.d.ts +3 -3
  121. package/dist/ui/Progress.js +23 -22
  122. package/dist/ui/Skills.d.ts +11 -0
  123. package/dist/ui/Skills.js +148 -0
  124. package/dist/ui/StackSelector.d.ts +2 -2
  125. package/dist/ui/StackSelector.js +26 -16
  126. package/dist/ui/Summary.d.ts +3 -3
  127. package/dist/ui/Summary.js +60 -50
  128. package/dist/ui/Welcome.d.ts +1 -1
  129. package/dist/ui/Welcome.js +15 -16
  130. package/dist/ui/theme.d.ts +1 -1
  131. package/dist/ui/theme.js +6 -6
  132. package/package.json +9 -6
  133. package/templates/common/atlassian/mcp-atlassian-snippet.json +16 -0
  134. package/templates/common/repoforge/mcp-repoforge-snippet.json +11 -0
  135. package/templates/common/repoforge/repoforge.yaml +34 -0
  136. package/templates/github/deploy-docker-zero-downtime.yml +140 -0
  137. package/templates/github/repoforge-graph.yml +45 -0
  138. package/templates/gitlab/deploy-docker-zero-downtime.yml +57 -0
  139. package/templates/local-ai/.env.example +17 -0
  140. package/templates/local-ai/docker-compose.yml +95 -0
  141. package/templates/security-hooks/claude-settings-security.json +30 -0
  142. package/templates/security-hooks/commit-msg-signing +29 -0
  143. package/templates/security-hooks/pre-commit-permissions +74 -0
  144. package/templates/security-hooks/pre-commit-secrets +74 -0
  145. package/templates/security-hooks/pre-push-branch-protection +62 -0
  146. package/templates/security-hooks/pre-push-deps +83 -0
  147. package/templates/security-hooks/pre-push-signing +67 -0
  148. package/templates/woodpecker/deploy-docker-zero-downtime.yml +50 -0
  149. package/templates/workflows/ci-pipeline.dot +15 -0
  150. package/templates/workflows/feature-flow.dot +21 -0
  151. package/templates/workflows/release.dot +16 -0
  152. package/dist/__integration__/helpers.d.ts +0 -20
  153. package/dist/__integration__/helpers.d.ts.map +0 -1
  154. package/dist/__integration__/helpers.js +0 -31
  155. package/dist/__integration__/helpers.js.map +0 -1
  156. package/dist/commands/analyze.d.ts.map +0 -1
  157. package/dist/commands/analyze.js.map +0 -1
  158. package/dist/commands/ci.d.ts.map +0 -1
  159. package/dist/commands/ci.js.map +0 -1
  160. package/dist/commands/doctor.d.ts.map +0 -1
  161. package/dist/commands/doctor.js.map +0 -1
  162. package/dist/commands/init.d.ts.map +0 -1
  163. package/dist/commands/init.js.map +0 -1
  164. package/dist/commands/llmstxt.d.ts.map +0 -1
  165. package/dist/commands/llmstxt.js.map +0 -1
  166. package/dist/commands/plugin.d.ts.map +0 -1
  167. package/dist/commands/plugin.js.map +0 -1
  168. package/dist/constants.d.ts.map +0 -1
  169. package/dist/constants.js.map +0 -1
  170. package/dist/index.d.ts.map +0 -1
  171. package/dist/index.js.map +0 -1
  172. package/dist/lib/common.d.ts.map +0 -1
  173. package/dist/lib/common.js.map +0 -1
  174. package/dist/lib/docker.d.ts.map +0 -1
  175. package/dist/lib/docker.js.map +0 -1
  176. package/dist/lib/frontmatter.d.ts.map +0 -1
  177. package/dist/lib/frontmatter.js.map +0 -1
  178. package/dist/lib/plugin.d.ts.map +0 -1
  179. package/dist/lib/plugin.js.map +0 -1
  180. package/dist/lib/template.d.ts.map +0 -1
  181. package/dist/lib/template.js.map +0 -1
  182. package/dist/types/index.d.ts.map +0 -1
  183. package/dist/types/index.js.map +0 -1
  184. package/dist/ui/AnalyzeUI.d.ts.map +0 -1
  185. package/dist/ui/AnalyzeUI.js.map +0 -1
  186. package/dist/ui/App.d.ts.map +0 -1
  187. package/dist/ui/App.js.map +0 -1
  188. package/dist/ui/CI.d.ts.map +0 -1
  189. package/dist/ui/CI.js.map +0 -1
  190. package/dist/ui/CIContext.d.ts.map +0 -1
  191. package/dist/ui/CIContext.js.map +0 -1
  192. package/dist/ui/CISelector.d.ts.map +0 -1
  193. package/dist/ui/CISelector.js.map +0 -1
  194. package/dist/ui/Doctor.d.ts.map +0 -1
  195. package/dist/ui/Doctor.js.map +0 -1
  196. package/dist/ui/Header.d.ts.map +0 -1
  197. package/dist/ui/Header.js.map +0 -1
  198. package/dist/ui/LlmsTxt.d.ts.map +0 -1
  199. package/dist/ui/LlmsTxt.js.map +0 -1
  200. package/dist/ui/MemorySelector.d.ts.map +0 -1
  201. package/dist/ui/MemorySelector.js.map +0 -1
  202. package/dist/ui/NameInput.d.ts.map +0 -1
  203. package/dist/ui/NameInput.js.map +0 -1
  204. package/dist/ui/OptionSelector.d.ts.map +0 -1
  205. package/dist/ui/OptionSelector.js.map +0 -1
  206. package/dist/ui/Plugin.d.ts.map +0 -1
  207. package/dist/ui/Plugin.js.map +0 -1
  208. package/dist/ui/Progress.d.ts.map +0 -1
  209. package/dist/ui/Progress.js.map +0 -1
  210. package/dist/ui/StackSelector.d.ts.map +0 -1
  211. package/dist/ui/StackSelector.js.map +0 -1
  212. package/dist/ui/Summary.d.ts.map +0 -1
  213. package/dist/ui/Summary.js.map +0 -1
  214. package/dist/ui/Welcome.d.ts.map +0 -1
  215. package/dist/ui/Welcome.js.map +0 -1
  216. package/dist/ui/theme.d.ts.map +0 -1
  217. package/dist/ui/theme.js.map +0 -1
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Render a WorkflowGraph as ASCII art.
3
+ * Linear pipelines render as: [lint] -> [test] -> [build]
4
+ * Branching renders with indentation.
5
+ * Optionally overlays validation results with pass/fail icons.
6
+ */
7
+ export function renderAscii(graph, validationResults) {
8
+ if (graph.nodes.length === 0)
9
+ return "(empty graph)";
10
+ const resultMap = new Map();
11
+ if (validationResults) {
12
+ for (const r of validationResults) {
13
+ resultMap.set(r.node, r);
14
+ }
15
+ }
16
+ // Build adjacency and in-degree
17
+ const adjacency = new Map();
18
+ const inDegree = new Map();
19
+ for (const node of graph.nodes) {
20
+ adjacency.set(node.id, []);
21
+ inDegree.set(node.id, 0);
22
+ }
23
+ for (const edge of graph.edges) {
24
+ const children = adjacency.get(edge.from);
25
+ if (children)
26
+ children.push(edge.to);
27
+ inDegree.set(edge.to, (inDegree.get(edge.to) ?? 0) + 1);
28
+ }
29
+ // Find roots (in-degree 0)
30
+ const roots = graph.nodes
31
+ .filter((n) => (inDegree.get(n.id) ?? 0) === 0)
32
+ .map((n) => n.id);
33
+ if (roots.length === 0) {
34
+ // Cycle — just list all nodes
35
+ return graph.nodes
36
+ .map((n) => formatNode(n.id, n.label, resultMap))
37
+ .join(" -> ");
38
+ }
39
+ // Topological order using Kahn's algorithm
40
+ const queue = [...roots];
41
+ const ordered = [];
42
+ const visited = new Set();
43
+ while (queue.length > 0) {
44
+ const current = queue.shift();
45
+ if (visited.has(current))
46
+ continue;
47
+ visited.add(current);
48
+ ordered.push(current);
49
+ const children = adjacency.get(current) ?? [];
50
+ for (const child of children) {
51
+ const deg = (inDegree.get(child) ?? 1) - 1;
52
+ inDegree.set(child, deg);
53
+ if (deg <= 0 && !visited.has(child)) {
54
+ queue.push(child);
55
+ }
56
+ }
57
+ }
58
+ // Add any unvisited nodes (disconnected)
59
+ for (const node of graph.nodes) {
60
+ if (!visited.has(node.id))
61
+ ordered.push(node.id);
62
+ }
63
+ // Build label map
64
+ const labelMap = new Map();
65
+ for (const node of graph.nodes) {
66
+ labelMap.set(node.id, node.label);
67
+ }
68
+ // Build edge label map
69
+ const edgeLabelMap = new Map();
70
+ for (const edge of graph.edges) {
71
+ const key = `${edge.from}->${edge.to}`;
72
+ if (edge.label)
73
+ edgeLabelMap.set(key, edge.label);
74
+ }
75
+ // Render: try linear first, fall back to tree-like
76
+ const lines = [];
77
+ lines.push(`# ${graph.name}`);
78
+ lines.push("");
79
+ // Check if it's a simple linear chain
80
+ const isLinear = graph.nodes.every((n) => {
81
+ const children = adjacency.get(n.id) ?? [];
82
+ return children.length <= 1;
83
+ }) && roots.length === 1;
84
+ if (isLinear) {
85
+ const chain = [];
86
+ let current = roots[0];
87
+ while (current) {
88
+ chain.push(formatNode(current, labelMap.get(current) ?? current, resultMap));
89
+ const children = adjacency.get(current) ?? [];
90
+ const next = children[0];
91
+ if (next) {
92
+ const edgeKey = `${current}->${next}`;
93
+ const edgeLabel = edgeLabelMap.get(edgeKey);
94
+ if (edgeLabel) {
95
+ chain.push(`--(${edgeLabel})-->`);
96
+ }
97
+ }
98
+ current = next;
99
+ }
100
+ lines.push(chain.join(" -> ").replace(/ -> --/g, " --"));
101
+ }
102
+ else {
103
+ // Tree-like rendering
104
+ for (const nodeId of ordered) {
105
+ const children = adjacency.get(nodeId) ?? [];
106
+ const nodeStr = formatNode(nodeId, labelMap.get(nodeId) ?? nodeId, resultMap);
107
+ if (children.length === 0) {
108
+ lines.push(` ${nodeStr}`);
109
+ }
110
+ else {
111
+ const childStrs = children.map((c) => {
112
+ const edgeKey = `${nodeId}->${c}`;
113
+ const edgeLabel = edgeLabelMap.get(edgeKey);
114
+ const arrow = edgeLabel ? `--(${edgeLabel})-->` : "->";
115
+ return `${arrow} ${formatNode(c, labelMap.get(c) ?? c, resultMap)}`;
116
+ });
117
+ if (childStrs.length === 1) {
118
+ lines.push(` ${nodeStr} ${childStrs[0]}`);
119
+ }
120
+ else {
121
+ lines.push(` ${nodeStr}`);
122
+ for (const cs of childStrs) {
123
+ lines.push(` ${cs}`);
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ // Add validation legend if results are present
130
+ if (validationResults && validationResults.length > 0) {
131
+ lines.push("");
132
+ lines.push("---");
133
+ for (const r of validationResults) {
134
+ const icon = r.status === "pass" ? "\u2713" : r.status === "fail" ? "\u2717" : "-";
135
+ lines.push(` ${icon} ${r.node}${r.detail ? `: ${r.detail}` : ""}`);
136
+ }
137
+ }
138
+ return lines.join("\n");
139
+ }
140
+ function formatNode(id, label, resultMap) {
141
+ const result = resultMap.get(id);
142
+ if (result) {
143
+ const icon = result.status === "pass"
144
+ ? "\u2713"
145
+ : result.status === "fail"
146
+ ? "\u2717"
147
+ : "?";
148
+ return `[${icon} ${label}]`;
149
+ }
150
+ return `[${label}]`;
151
+ }
152
+ //# sourceMappingURL=renderer.js.map
@@ -0,0 +1,10 @@
1
+ import type { WorkflowGraph, WorkflowValidationResult } from "../../types/index.js";
2
+ /**
3
+ * Validate a project directory against a workflow graph.
4
+ * Nodes with a `check` attribute are evaluated against built-in checks.
5
+ * Nodes without a `check` attribute are skipped.
6
+ */
7
+ export declare function validateWorkflow(graph: WorkflowGraph, projectDir: string): Promise<WorkflowValidationResult[]>;
8
+ /** Expose check names for testing and documentation */
9
+ export declare function getAvailableChecks(): string[];
10
+ //# sourceMappingURL=validator.d.ts.map
@@ -0,0 +1,189 @@
1
+ import fs from "fs-extra";
2
+ import path from "path";
3
+ /**
4
+ * Built-in check functions that map node `check` attributes to project validations.
5
+ * Each check receives the project directory and returns { pass, detail }.
6
+ */
7
+ const BUILTIN_CHECKS = {
8
+ "has-linter": async (dir) => {
9
+ const eslintConfig = [
10
+ ".eslintrc",
11
+ ".eslintrc.js",
12
+ ".eslintrc.json",
13
+ ".eslintrc.yml",
14
+ "eslint.config.js",
15
+ "eslint.config.mjs",
16
+ "eslint.config.ts",
17
+ ];
18
+ const ruffConfig = ["ruff.toml", ".ruff.toml"];
19
+ const golangci = [".golangci.yml", ".golangci.yaml"];
20
+ const configs = [...eslintConfig, ...ruffConfig, ...golangci];
21
+ for (const cfg of configs) {
22
+ if (await fs.pathExists(path.join(dir, cfg))) {
23
+ return { pass: true, detail: `Found ${cfg}` };
24
+ }
25
+ }
26
+ // Check package.json for eslint dependency
27
+ const pkgPath = path.join(dir, "package.json");
28
+ if (await fs.pathExists(pkgPath)) {
29
+ try {
30
+ const pkg = (await fs.readJson(pkgPath));
31
+ const deps = {
32
+ ...pkg["devDependencies"],
33
+ ...pkg["dependencies"],
34
+ };
35
+ if (deps["eslint"] || deps["biome"] || deps["oxlint"]) {
36
+ return { pass: true, detail: "Linter found in package.json" };
37
+ }
38
+ }
39
+ catch {
40
+ /* ignore */
41
+ }
42
+ }
43
+ return { pass: false, detail: "No linter configuration found" };
44
+ },
45
+ "has-tests": async (dir) => {
46
+ const testPatterns = [
47
+ "src/**/*.test.ts",
48
+ "src/**/*.test.tsx",
49
+ "src/**/*.spec.ts",
50
+ "tests/",
51
+ "test/",
52
+ "__tests__/",
53
+ "src/**/*_test.go",
54
+ "*_test.go",
55
+ "tests/test_*.py",
56
+ "test_*.py",
57
+ ];
58
+ for (const pattern of testPatterns) {
59
+ const target = path.join(dir, pattern.split("*")[0] ?? "");
60
+ if (await fs.pathExists(target)) {
61
+ return {
62
+ pass: true,
63
+ detail: `Found test path: ${pattern.split("*")[0]}`,
64
+ };
65
+ }
66
+ }
67
+ return { pass: false, detail: "No test files or directories found" };
68
+ },
69
+ "has-ci": async (dir) => {
70
+ const ciPaths = [
71
+ ".github/workflows",
72
+ ".gitlab-ci.yml",
73
+ ".woodpecker.yml",
74
+ ".woodpecker/",
75
+ "Jenkinsfile",
76
+ ".circleci/config.yml",
77
+ ];
78
+ for (const p of ciPaths) {
79
+ if (await fs.pathExists(path.join(dir, p))) {
80
+ return { pass: true, detail: `Found ${p}` };
81
+ }
82
+ }
83
+ return { pass: false, detail: "No CI configuration found" };
84
+ },
85
+ "has-dockerfile": async (dir) => {
86
+ const dockerfiles = [
87
+ "Dockerfile",
88
+ "docker-compose.yml",
89
+ "docker-compose.yaml",
90
+ "compose.yml",
91
+ "compose.yaml",
92
+ ];
93
+ for (const f of dockerfiles) {
94
+ if (await fs.pathExists(path.join(dir, f))) {
95
+ return { pass: true, detail: `Found ${f}` };
96
+ }
97
+ }
98
+ return { pass: false, detail: "No Dockerfile or compose file found" };
99
+ },
100
+ "has-security": async (dir) => {
101
+ const secPaths = [
102
+ ".javi-forge/security-baseline.json",
103
+ "semgrep.yml",
104
+ ".semgrep.yml",
105
+ ".snyk",
106
+ ];
107
+ for (const p of secPaths) {
108
+ if (await fs.pathExists(path.join(dir, p))) {
109
+ return { pass: true, detail: `Found ${p}` };
110
+ }
111
+ }
112
+ return { pass: false, detail: "No security scanning configuration found" };
113
+ },
114
+ "has-docs": async (dir) => {
115
+ const docPaths = ["docs/", "README.md", "doc/", "documentation/"];
116
+ for (const p of docPaths) {
117
+ if (await fs.pathExists(path.join(dir, p))) {
118
+ return { pass: true, detail: `Found ${p}` };
119
+ }
120
+ }
121
+ return { pass: false, detail: "No documentation found" };
122
+ },
123
+ "has-changelog": async (dir) => {
124
+ const files = ["CHANGELOG.md", "CHANGES.md", "HISTORY.md"];
125
+ for (const f of files) {
126
+ if (await fs.pathExists(path.join(dir, f))) {
127
+ return { pass: true, detail: `Found ${f}` };
128
+ }
129
+ }
130
+ return { pass: false, detail: "No changelog found" };
131
+ },
132
+ "has-license": async (dir) => {
133
+ const files = ["LICENSE", "LICENSE.md", "LICENSE.txt", "LICENCE"];
134
+ for (const f of files) {
135
+ if (await fs.pathExists(path.join(dir, f))) {
136
+ return { pass: true, detail: `Found ${f}` };
137
+ }
138
+ }
139
+ return { pass: false, detail: "No license file found" };
140
+ },
141
+ };
142
+ /**
143
+ * Validate a project directory against a workflow graph.
144
+ * Nodes with a `check` attribute are evaluated against built-in checks.
145
+ * Nodes without a `check` attribute are skipped.
146
+ */
147
+ export async function validateWorkflow(graph, projectDir) {
148
+ const results = [];
149
+ for (const node of graph.nodes) {
150
+ if (!node.check) {
151
+ results.push({
152
+ node: node.id,
153
+ status: "skip",
154
+ detail: "No check defined",
155
+ });
156
+ continue;
157
+ }
158
+ const checkFn = BUILTIN_CHECKS[node.check];
159
+ if (!checkFn) {
160
+ results.push({
161
+ node: node.id,
162
+ status: "skip",
163
+ detail: `Unknown check: ${node.check}`,
164
+ });
165
+ continue;
166
+ }
167
+ try {
168
+ const { pass, detail } = await checkFn(projectDir);
169
+ results.push({
170
+ node: node.id,
171
+ status: pass ? "pass" : "fail",
172
+ detail,
173
+ });
174
+ }
175
+ catch (e) {
176
+ results.push({
177
+ node: node.id,
178
+ status: "fail",
179
+ detail: `Check error: ${e instanceof Error ? e.message : String(e)}`,
180
+ });
181
+ }
182
+ }
183
+ return results;
184
+ }
185
+ /** Expose check names for testing and documentation */
186
+ export function getAvailableChecks() {
187
+ return Object.keys(BUILTIN_CHECKS);
188
+ }
189
+ //# sourceMappingURL=validator.js.map
@@ -0,0 +1,4 @@
1
+ export { generateTaskId, createTask, addSubtask, buildTaskTree, findTask, type TaskNode, type TaskStatus, } from './task-id.js';
2
+ export { TaskTracker } from './task-tracker.js';
3
+ export { getDefaultScaffoldTasks } from './scaffold-tasks.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,4 @@
1
+ export { generateTaskId, createTask, addSubtask, buildTaskTree, findTask, } from './task-id.js';
2
+ export { TaskTracker } from './task-tracker.js';
3
+ export { getDefaultScaffoldTasks } from './scaffold-tasks.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,3 @@
1
+ import { type TaskNode } from './task-id.js';
2
+ export declare function getDefaultScaffoldTasks(): TaskNode[];
3
+ //# sourceMappingURL=scaffold-tasks.d.ts.map
@@ -0,0 +1,14 @@
1
+ import { createTask, addSubtask } from './task-id.js';
2
+ export function getDefaultScaffoldTasks() {
3
+ const setup = createTask({ title: 'Setup project structure' });
4
+ addSubtask(setup, { title: 'Initialize git repository' });
5
+ addSubtask(setup, { title: 'Configure TypeScript' });
6
+ addSubtask(setup, { title: 'Setup linter and formatter' });
7
+ const testing = createTask({ title: 'Setup testing' });
8
+ addSubtask(testing, { title: 'Configure test runner' });
9
+ addSubtask(testing, { title: 'Write initial tests' });
10
+ const ci = createTask({ title: 'Create CI pipeline' });
11
+ const deploy = createTask({ title: 'Deploy to production' });
12
+ return [setup, testing, ci, deploy];
13
+ }
14
+ //# sourceMappingURL=scaffold-tasks.js.map
@@ -0,0 +1,30 @@
1
+ export type TaskStatus = 'todo' | 'in-progress' | 'done';
2
+ export type TaskNode = {
3
+ id: string;
4
+ title: string;
5
+ description?: string;
6
+ status: TaskStatus;
7
+ parentId?: string;
8
+ children: TaskNode[];
9
+ dependencies: string[];
10
+ assignee?: string;
11
+ createdAt: string;
12
+ updatedAt: string;
13
+ };
14
+ export declare function generateTaskId(content: {
15
+ title: string;
16
+ description?: string;
17
+ parentId?: string;
18
+ }): string;
19
+ export declare function createTask(params: {
20
+ title: string;
21
+ description?: string;
22
+ parentId?: string;
23
+ }): TaskNode;
24
+ export declare function addSubtask(parent: TaskNode, childParams: {
25
+ title: string;
26
+ description?: string;
27
+ }): TaskNode;
28
+ export declare function buildTaskTree(flat: TaskNode[]): TaskNode[];
29
+ export declare function findTask(tree: TaskNode[], id: string): TaskNode | undefined;
30
+ //# sourceMappingURL=task-id.d.ts.map
@@ -0,0 +1,55 @@
1
+ import { createHash } from 'node:crypto';
2
+ export function generateTaskId(content) {
3
+ const input = [content.title, content.description ?? '', content.parentId ?? ''].join('|');
4
+ return createHash('sha256').update(input).digest('hex').slice(0, 7);
5
+ }
6
+ export function createTask(params) {
7
+ const now = new Date().toISOString();
8
+ return {
9
+ id: generateTaskId(params),
10
+ title: params.title,
11
+ description: params.description,
12
+ status: 'todo',
13
+ parentId: params.parentId,
14
+ children: [],
15
+ dependencies: [],
16
+ createdAt: now,
17
+ updatedAt: now,
18
+ };
19
+ }
20
+ export function addSubtask(parent, childParams) {
21
+ const child = createTask({
22
+ title: childParams.title,
23
+ description: childParams.description,
24
+ parentId: parent.id,
25
+ });
26
+ parent.children.push(child);
27
+ return child;
28
+ }
29
+ export function buildTaskTree(flat) {
30
+ const map = new Map();
31
+ for (const node of flat) {
32
+ map.set(node.id, { ...node, children: [] });
33
+ }
34
+ const roots = [];
35
+ for (const node of map.values()) {
36
+ if (node.parentId && map.has(node.parentId)) {
37
+ map.get(node.parentId).children.push(node);
38
+ }
39
+ else {
40
+ roots.push(node);
41
+ }
42
+ }
43
+ return roots;
44
+ }
45
+ export function findTask(tree, id) {
46
+ for (const node of tree) {
47
+ if (node.id === id)
48
+ return node;
49
+ const found = findTask(node.children, id);
50
+ if (found)
51
+ return found;
52
+ }
53
+ return undefined;
54
+ }
55
+ //# sourceMappingURL=task-id.js.map
@@ -0,0 +1,15 @@
1
+ import { type TaskNode, type TaskStatus } from './task-id.js';
2
+ export declare class TaskTracker {
3
+ private tasks;
4
+ addTask(title: string, description?: string, parentId?: string): TaskNode;
5
+ updateStatus(id: string, status: TaskStatus): void;
6
+ assignTask(id: string, assignee: string): void;
7
+ addDependency(taskId: string, dependsOnId: string): void;
8
+ getBlockedTasks(): TaskNode[];
9
+ getReadyTasks(): TaskNode[];
10
+ toMarkdown(): string;
11
+ toJSON(): string;
12
+ static fromJSON(json: string): TaskTracker;
13
+ private findFlat;
14
+ }
15
+ //# sourceMappingURL=task-tracker.d.ts.map
@@ -0,0 +1,81 @@
1
+ import { createTask } from './task-id.js';
2
+ export class TaskTracker {
3
+ tasks = [];
4
+ addTask(title, description, parentId) {
5
+ const task = createTask({ title, description, parentId });
6
+ if (parentId) {
7
+ const parent = this.findFlat(parentId);
8
+ if (parent) {
9
+ parent.children.push(task);
10
+ }
11
+ }
12
+ this.tasks.push(task);
13
+ return task;
14
+ }
15
+ updateStatus(id, status) {
16
+ const task = this.findFlat(id);
17
+ if (!task)
18
+ throw new Error(`Task "${id}" not found`);
19
+ task.status = status;
20
+ task.updatedAt = new Date().toISOString();
21
+ }
22
+ assignTask(id, assignee) {
23
+ const task = this.findFlat(id);
24
+ if (!task)
25
+ throw new Error(`Task "${id}" not found`);
26
+ task.assignee = assignee;
27
+ task.updatedAt = new Date().toISOString();
28
+ }
29
+ addDependency(taskId, dependsOnId) {
30
+ const task = this.findFlat(taskId);
31
+ if (!task)
32
+ throw new Error(`Task "${taskId}" not found`);
33
+ const dep = this.findFlat(dependsOnId);
34
+ if (!dep)
35
+ throw new Error(`Dependency "${dependsOnId}" not found`);
36
+ task.dependencies.push(dependsOnId);
37
+ }
38
+ getBlockedTasks() {
39
+ return this.tasks.filter((t) => {
40
+ if (t.dependencies.length === 0)
41
+ return false;
42
+ return t.dependencies.some((depId) => {
43
+ const dep = this.findFlat(depId);
44
+ return !dep || dep.status !== 'done';
45
+ });
46
+ });
47
+ }
48
+ getReadyTasks() {
49
+ return this.tasks.filter((t) => {
50
+ if (t.status !== 'todo')
51
+ return false;
52
+ if (t.dependencies.length === 0)
53
+ return true;
54
+ return t.dependencies.every((depId) => {
55
+ const dep = this.findFlat(depId);
56
+ return dep?.status === 'done';
57
+ });
58
+ });
59
+ }
60
+ toMarkdown() {
61
+ return this.tasks
62
+ .map((t) => {
63
+ const check = t.status === 'done' ? 'x' : ' ';
64
+ const assignee = t.assignee ? ` @${t.assignee}` : '';
65
+ return `- [${check}] ${t.title}${assignee}`;
66
+ })
67
+ .join('\n');
68
+ }
69
+ toJSON() {
70
+ return JSON.stringify(this.tasks);
71
+ }
72
+ static fromJSON(json) {
73
+ const tracker = new TaskTracker();
74
+ tracker.tasks = JSON.parse(json);
75
+ return tracker;
76
+ }
77
+ findFlat(id) {
78
+ return this.tasks.find((t) => t.id === id);
79
+ }
80
+ }
81
+ //# sourceMappingURL=task-tracker.js.map