@davidbalzan/groundwork 0.3.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 (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +323 -0
  3. package/docs/DECISIONS.md +170 -0
  4. package/package.json +38 -0
  5. package/payload/doc-templates/COMMANDS.md +419 -0
  6. package/payload/doc-templates/DECISIONS.md +168 -0
  7. package/payload/doc-templates/FACTS.md +43 -0
  8. package/payload/doc-templates/GROUNDWORK_METHODOLOGY.md +1300 -0
  9. package/payload/doc-templates/STACK_MAP.md +90 -0
  10. package/payload/doc-templates/WORKSTREAMS.md +79 -0
  11. package/payload/doc-templates/_INDEX.md +54 -0
  12. package/payload/doc-templates/phases/README.md +36 -0
  13. package/payload/doc-templates/phases/templates/README.md +63 -0
  14. package/payload/doc-templates/phases/templates/TASK_TEMPLATE.md +302 -0
  15. package/payload/doc-templates/phases/templates/task_template_prompt.md +229 -0
  16. package/payload/doc-templates/templates/ARCHITECTURE_GUIDE_TEMPLATE.md +250 -0
  17. package/payload/doc-templates/templates/DESIGN_SYSTEM_TEMPLATE.md +336 -0
  18. package/payload/doc-templates/templates/DONE_TEMPLATE.md +21 -0
  19. package/payload/doc-templates/templates/PHASES_README_TEMPLATE.md +144 -0
  20. package/payload/doc-templates/templates/PHASE_README_TEMPLATE.md +142 -0
  21. package/payload/doc-templates/templates/PRD_TEMPLATE.md +348 -0
  22. package/payload/doc-templates/templates/PRODUCTION_ROADMAP_TEMPLATE.md +168 -0
  23. package/payload/doc-templates/templates/QUEUE_TEMPLATE.md +17 -0
  24. package/payload/doc-templates/templates/TECH_STACK_TEMPLATE.md +199 -0
  25. package/payload/scripts/check-task.mjs +98 -0
  26. package/payload/scripts/check-versions.mjs +113 -0
  27. package/payload/scripts/phase-status.mjs +69 -0
  28. package/payload/scripts/set-fact.mjs +86 -0
  29. package/payload/skills/add-data-layer/SKILL.md +129 -0
  30. package/payload/skills/check-task/SKILL.md +35 -0
  31. package/payload/skills/check-versions/SKILL.md +47 -0
  32. package/payload/skills/create-prd/SKILL.md +90 -0
  33. package/payload/skills/domain-model/SKILL.md +90 -0
  34. package/payload/skills/kickstart/SKILL.md +157 -0
  35. package/payload/skills/log-decision/SKILL.md +65 -0
  36. package/payload/skills/next/SKILL.md +65 -0
  37. package/payload/skills/plan-phase/SKILL.md +108 -0
  38. package/payload/skills/remember/SKILL.md +77 -0
  39. package/payload/skills/start-session/SKILL.md +52 -0
  40. package/payload/skills/update-workstreams/SKILL.md +60 -0
  41. package/src/cli.mjs +115 -0
  42. package/src/commands/add.mjs +39 -0
  43. package/src/commands/artifacts.mjs +24 -0
  44. package/src/commands/doctor.mjs +292 -0
  45. package/src/commands/init.mjs +147 -0
  46. package/src/commands/knowledge.mjs +148 -0
  47. package/src/commands/list.mjs +61 -0
  48. package/src/commands/status.mjs +96 -0
  49. package/src/commands/update.mjs +128 -0
  50. package/src/lib/adr-tripwire.mjs +171 -0
  51. package/src/lib/artifacts.mjs +124 -0
  52. package/src/lib/config.mjs +43 -0
  53. package/src/lib/fs.mjs +46 -0
  54. package/src/lib/log.mjs +22 -0
  55. package/src/lib/paths.mjs +36 -0
  56. package/src/lib/progress.mjs +26 -0
  57. package/src/lib/skills.mjs +42 -0
@@ -0,0 +1,199 @@
1
+ ---
2
+ title: "Tech Stack Template"
3
+ tags: [groundwork/template]
4
+ aliases: []
5
+ ---
6
+
7
+ # [Project Name] - Complete Tech Stack
8
+
9
+ > Single source of truth for all technology choices, versions, and rationale. Referenced by AI agents when generating code, suggesting patterns, or debugging compatibility issues.
10
+
11
+ ---
12
+
13
+ ## ๐Ÿ“‹ Overview
14
+
15
+ **Project Type**: [Web app / API / CLI / Library / Mobile โ€” describe what this project is and what problem it solves]
16
+ **Architecture**: [Monorepo / Monolith / Microservices โ€” describe the high-level architectural approach and why it was chosen]
17
+ **Primary Language**: [TypeScript / Python / Go / etc. โ€” include version and strict mode configuration]
18
+
19
+ ---
20
+
21
+ ## ๐Ÿ—๏ธ Architecture
22
+
23
+ ### Project Structure
24
+
25
+ ```
26
+ [project-name]/
27
+ โ”œโ”€โ”€ apps/
28
+ โ”‚ โ”œโ”€โ”€ [app-1]/ # [Purpose: what this app does and who uses it]
29
+ โ”‚ โ””โ”€โ”€ [app-2]/ # [Purpose: what this app does and who uses it]
30
+ โ”œโ”€โ”€ packages/
31
+ โ”‚ โ”œโ”€โ”€ [package-1]/ # [Purpose: what shared functionality this provides]
32
+ โ”‚ โ””โ”€โ”€ [package-2]/ # [Purpose: what shared functionality this provides]
33
+ โ””โ”€โ”€ docs/ # Project documentation and templates
34
+ ```
35
+
36
+ ### Data Flow
37
+
38
+ ```
39
+ [Describe the primary data flow through the system, e.g.:]
40
+ [User โ†’ Browser โ†’ Frontend (React) โ†’ API (Hono) โ†’ Database (PostgreSQL)]
41
+ [ โ† JSON Response โ† ]
42
+ ```
43
+
44
+ ---
45
+
46
+ ## ๐ŸŽจ Frontend Stack ([app location, e.g., apps/web/])
47
+
48
+ ### Core Framework & Build
49
+
50
+ | Technology | Version | Purpose | Why This Choice |
51
+ | ------------------------ | --------------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
52
+ | [Framework, e.g., React] | [version, e.g., 19.x] | [UI framework โ€” describe rendering model, e.g., "Component-based UI with concurrent rendering"] | [Rationale: ecosystem size, team familiarity, performance characteristics] |
53
+ | [Build Tool, e.g., Vite] | [version, e.g., 6.x] | [Build & dev server โ€” describe key features, e.g., "HMR, ESM-native, plugin ecosystem"] | [Rationale: speed vs alternatives, plugin availability] |
54
+ | TypeScript | [version, e.g., 5.x] | [Type safety โ€” describe strictness level and key compiler options] | [Rationale: error prevention, DX, refactoring confidence] |
55
+
56
+ ### Styling & UI
57
+
58
+ | Technology | Version | Purpose | Why This Choice |
59
+ | ----------------------------------- | -------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------------------- |
60
+ | [CSS Framework, e.g., Tailwind CSS] | [version, e.g., 4.x] | [Styling approach โ€” describe methodology: utility-first, CSS-in-JS, BEM, etc.] | [Rationale: bundle size, DX, design system alignment] |
61
+ | [UI Library, e.g., Lucide React] | [version] | [Icon/component library โ€” describe what it provides and customization approach] | [Rationale: consistency, bundle size, tree-shaking support] |
62
+
63
+ ### State Management
64
+
65
+ | Technology | Version | Purpose | Why This Choice |
66
+ | ---------------------------------------------- | --------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
67
+ | [State Library, e.g., Zustand / React Context] | [version] | [State management scope โ€” describe what state it manages: global, server, form, etc.] | [Rationale: complexity vs simplicity, bundle size, learning curve] |
68
+
69
+ ### Key Frontend Patterns
70
+
71
+ - **Routing**: [Library and approach โ€” file-based, config-based, or framework-native]
72
+ - **Data Fetching**: [Library and strategy โ€” SWR, React Query, fetch, or framework-native]
73
+ - **Form Handling**: [Library or approach โ€” controlled, uncontrolled, form library name]
74
+ - **Error Boundaries**: [Strategy for handling runtime errors in the UI]
75
+
76
+ ---
77
+
78
+ ## โš™๏ธ Backend Stack ([app location, e.g., apps/api/])
79
+
80
+ ### Core Framework & Runtime
81
+
82
+ | Technology | Version | Purpose | Why This Choice |
83
+ | ------------------------ | --------------------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
84
+ | [Runtime, e.g., Node.js] | [version, e.g., 22.x] | [Server runtime โ€” describe key features: event loop, worker threads, native ESM, etc.] | [Rationale: performance, ecosystem, deployment target compatibility] |
85
+ | [Framework, e.g., Hono] | [version, e.g., 4.x] | [Web framework โ€” describe middleware model, routing, and key features] | [Rationale: performance benchmarks, type safety, portability across runtimes] |
86
+ | TypeScript | [version] | [Shared with frontend โ€” note any backend-specific compiler options] | [Rationale: end-to-end type safety with shared package] |
87
+
88
+ ### Database & ORM
89
+
90
+ | Technology | Version | Purpose | Why This Choice |
91
+ | ---------------------------- | --------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
92
+ | [Database, e.g., PostgreSQL] | [version, e.g., 17.x] | [Primary data store โ€” describe data model: relational, document, key-value, etc.] | [Rationale: ACID compliance, scalability, feature set, managed hosting options] |
93
+ | [ORM, e.g., Drizzle] | [version] | [Database toolkit โ€” describe query approach: query builder, ORM, raw SQL, etc.] | [Rationale: type safety, migration support, performance, learning curve] |
94
+
95
+ ### Key Backend Patterns
96
+
97
+ - **Authentication**: [Strategy and library โ€” JWT, sessions, OAuth provider, etc.]
98
+ - **Validation**: [Library and approach โ€” Zod, Joi, class-validator, etc. and where validation occurs]
99
+ - **Error Handling**: [Strategy โ€” centralized error handler, error classes, HTTP status mapping]
100
+ - **Logging**: [Library and format โ€” structured JSON, log levels, correlation IDs]
101
+ - **API Documentation**: [Approach โ€” OpenAPI/Swagger, auto-generated, manual, etc.]
102
+
103
+ ---
104
+
105
+ ## ๐Ÿ”ง Infrastructure
106
+
107
+ ### Package Management & Monorepo
108
+
109
+ | Technology | Version | Purpose | Why This Choice |
110
+ | -------------------------------- | --------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
111
+ | [Package Manager, e.g., pnpm] | [version] | [Dependency management โ€” describe key features: workspaces, hoisting strategy, lockfile format] | [Rationale: disk space, speed, monorepo support] |
112
+ | [Monorepo Tool, e.g., Turborepo] | [version] | [Build orchestration โ€” describe key features: caching, parallel tasks, dependency graph] | [Rationale: build speed, DX, configuration simplicity] |
113
+
114
+ ### Development Environment
115
+
116
+ | Service | URL | Purpose |
117
+ | --------------------------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------- |
118
+ | Frontend | http://localhost:[port, e.g., 5173] | [Dev server with HMR โ€” describe proxy configuration if applicable] |
119
+ | Backend API | http://localhost:[port, e.g., 3000] | [API server โ€” describe auto-reload setup] |
120
+ | Database | localhost:[port, e.g., 5432] | [Local database โ€” describe how it's provisioned: Docker, native install, cloud] |
121
+ | [Additional services like Redis, mail server, etc.] | localhost:[port] | [Purpose and provisioning method] |
122
+
123
+ ### Deployment & CI/CD
124
+
125
+ - **Hosting**: [Platform and approach โ€” Vercel, AWS, Docker, self-hosted, etc.]
126
+ - **CI/CD**: [Pipeline tool โ€” GitHub Actions, GitLab CI, etc. and key stages]
127
+ - **Environments**: [List environments โ€” dev, staging, production and how they differ]
128
+ - **Containerization**: [Docker setup if applicable โ€” base images, compose services]
129
+
130
+ ---
131
+
132
+ ## ๐Ÿ“Š Dependency Summary
133
+
134
+ ### Production Dependencies (Critical Path)
135
+
136
+ | Package | Version | Location | Purpose | Upgrade Risk |
137
+ | -------------- | ------------------------------ | --------------------------- | -------------------------------------------- | ------------------------------------------------- |
138
+ | [Package name] | [Exact version, e.g., ^19.0.0] | [Which app/package uses it] | [What it does in this project โ€” be specific] | [Low/Medium/High โ€” describe breaking change risk] |
139
+ | [Package name] | [version] | [location] | [purpose] | [risk] |
140
+ | [Package name] | [version] | [location] | [purpose] | [risk] |
141
+
142
+ ### Development Dependencies (Tooling)
143
+
144
+ | Package | Version | Purpose |
145
+ | --------------------------- | --------- | ------------------------------------------------------------- |
146
+ | [Dev dep, e.g., typescript] | [version] | [Compiler/transpiler โ€” describe configuration approach] |
147
+ | [Dev dep, e.g., eslint] | [version] | [Linter โ€” describe config format: flat config, extends, etc.] |
148
+ | [Dev dep, e.g., prettier] | [version] | [Formatter โ€” describe key settings that differ from defaults] |
149
+
150
+ ---
151
+
152
+ ## ๐Ÿš€ Development Commands
153
+
154
+ ```bash
155
+ # Setup
156
+ [package-manager] install # Install all workspace dependencies
157
+
158
+ # Development
159
+ [package-manager] dev # Start all apps in development mode (parallel)
160
+ [package-manager] dev --filter=[app] # Start a specific app only
161
+
162
+ # Quality
163
+ [package-manager] build # Build all packages and apps for production
164
+ [package-manager] lint # Run linter across all workspaces
165
+ [package-manager] typecheck # TypeScript type checking (no emit)
166
+ [package-manager] format # Run code formatter
167
+ [package-manager] test # Run test suite [describe framework: vitest, jest, etc.]
168
+
169
+ # Database (if applicable)
170
+ [package-manager] db:migrate # Run pending database migrations
171
+ [package-manager] db:seed # Seed database with development data
172
+ [package-manager] db:studio # Open database GUI tool
173
+ ```
174
+
175
+ ---
176
+
177
+ ## โš ๏ธ Known Limitations & Future Upgrades
178
+
179
+ | Current Limitation | Impact | Planned Upgrade | When |
180
+ | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | --------------------------------------------------------------------- | --------------------------------------------------------- |
181
+ | [Limitation โ€” describe what doesn't work or isn't ideal, e.g., "No server-side rendering"] | [Impact on users/DX โ€” e.g., "Slower initial page load, no SEO"] | [Planned solution โ€” e.g., "Migrate to Next.js or add SSR middleware"] | [Trigger condition โ€” e.g., "When SEO becomes a priority"] |
182
+ | [Limitation] | [Impact] | [Planned upgrade] | [When to upgrade] |
183
+
184
+ ---
185
+
186
+ ## ๐Ÿ“ Notes
187
+
188
+ - [Version pinning strategy โ€” describe whether you pin exact versions or use ranges and why]
189
+ - [Browser support targets โ€” describe minimum browser versions if applicable]
190
+ - [Node.js version management โ€” describe .nvmrc, volta, or other version management]
191
+ - [Environment variables โ€” reference .env.example and describe required vs optional vars]
192
+
193
+ ---
194
+
195
+ ## ๐Ÿ”— Related Documents
196
+
197
+ - **[[ARCHITECTURE_GUIDE|Architecture Guide]]** - Why these technologies work together
198
+ - **[[DECISIONS|Decisions Log]]** - ADRs for each major technology choice
199
+ - **[[DESIGN_SYSTEM|Design System]]** - Visual language built on this stack
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * check-task โ€” flip a task checkbox to done and recompute progress.
4
+ * Shipped into a Groundwork project at docs/.groundwork/scripts/.
5
+ * The /check-task skill calls this so checkbox math is deterministic.
6
+ *
7
+ * Usage:
8
+ * node check-task.mjs <task-id|text> [path/to/PHASEN_TASKS.md]
9
+ * node check-task.mjs 2.3
10
+ * node check-task.mjs "implement auth middleware" docs/phases/phase2/PHASE2_TASKS.md
11
+ */
12
+ import fs from "node:fs";
13
+ import path from "node:path";
14
+
15
+ const [needle, explicitFile] = process.argv.slice(2);
16
+ if (!needle) {
17
+ console.error("Usage: node check-task.mjs <task-id|text> [tasks-file]");
18
+ process.exit(1);
19
+ }
20
+
21
+ const file = explicitFile || findTasksFile();
22
+ if (!file || !fs.existsSync(file)) {
23
+ console.error("โœ— No phase tasks file found. Pass one explicitly.");
24
+ process.exit(1);
25
+ }
26
+
27
+ const lines = fs.readFileSync(file, "utf8").split("\n");
28
+ const isId = /^\d+(\.\d+)*$/.test(needle);
29
+ let hit = -1;
30
+
31
+ for (let i = 0; i < lines.length; i++) {
32
+ if (!/^\s*-\s*\[ \]/.test(lines[i])) continue;
33
+ const text = lines[i].toLowerCase();
34
+ if (isId) {
35
+ // Match "2.3" as a token (start of task text or "N.M:")
36
+ if (new RegExp(`\\b${needle.replace(".", "\\.")}\\b`).test(lines[i])) {
37
+ hit = i;
38
+ break;
39
+ }
40
+ } else if (text.includes(needle.toLowerCase())) {
41
+ hit = i;
42
+ break;
43
+ }
44
+ }
45
+
46
+ if (hit === -1) {
47
+ console.error(`โœ— No open task matching "${needle}" in ${file}`);
48
+ process.exit(1);
49
+ }
50
+
51
+ lines[hit] = lines[hit].replace(/\[ \]/, "[x]");
52
+ const updated = recomputeProgress(lines.join("\n"));
53
+ fs.writeFileSync(file, updated.text);
54
+
55
+ console.log(`โœ“ Marked done: ${lines[hit].replace(/^\s*-\s*\[x\]\s*/, "").trim()}`);
56
+ console.log(
57
+ ` Progress: ${updated.done}/${updated.total} (${updated.pct}%) ${bar(updated.pct)}`
58
+ );
59
+ if (updated.done === updated.total)
60
+ console.log(" ๐ŸŽ‰ All tasks complete โ€” consider updating the roadmap & QUEUE/DONE.");
61
+
62
+ // ---------- helpers ----------
63
+ function findTasksFile() {
64
+ const root = "docs/phases";
65
+ if (!fs.existsSync(root)) return null;
66
+ const found = [];
67
+ (function walk(d) {
68
+ for (const e of fs.readdirSync(d, { withFileTypes: true })) {
69
+ const p = path.join(d, e.name);
70
+ if (e.isDirectory()) walk(p);
71
+ else if (/PHASE.*TASKS\.md$/i.test(e.name)) found.push(p);
72
+ }
73
+ })(root);
74
+ // Prefer the file with the most open checkboxes (the active phase).
75
+ return found.sort((a, b) => openCount(b) - openCount(a))[0] || null;
76
+ }
77
+ function openCount(f) {
78
+ return (fs.readFileSync(f, "utf8").match(/^\s*-\s*\[ \]/gm) || []).length;
79
+ }
80
+ function recomputeProgress(text) {
81
+ let done = 0,
82
+ total = 0;
83
+ for (const l of text.split("\n")) {
84
+ if (/^\s*-\s*\[[xX]\]/.test(l)) (done++, total++);
85
+ else if (/^\s*-\s*\[ \]/.test(l)) total++;
86
+ }
87
+ const pct = total ? Math.round((done / total) * 100) : 0;
88
+ // Update an existing "Overall Progress" line if present.
89
+ text = text.replace(
90
+ /(\*\*Overall Progress\*\*:).*$/m,
91
+ `$1 ${done}/${total} tasks (${pct}%)`
92
+ );
93
+ return { text, done, total, pct };
94
+ }
95
+ function bar(pct, w = 20) {
96
+ const f = Math.round((pct / 100) * w);
97
+ return "โ–ˆ".repeat(f) + "โ–‘".repeat(Math.max(0, w - f));
98
+ }
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * check-versions โ€” compare the project's pinned dependencies against the latest
4
+ * stable on the npm registry, so docs/STACK_MAP.md never silently goes stale.
5
+ * Shipped into a Groundwork project at docs/.groundwork/scripts/.
6
+ *
7
+ * Usage:
8
+ * node check-versions.mjs # check the curated stack present in package.json(s)
9
+ * node check-versions.mjs --all # check every dependency
10
+ * node check-versions.mjs react vite # check specific packages
11
+ *
12
+ * Exit code: 0 if all up to date, 1 if any are behind (useful in CI / bootstrap).
13
+ */
14
+ import { execFileSync } from "node:child_process";
15
+ import fs from "node:fs";
16
+ import path from "node:path";
17
+
18
+ const args = process.argv.slice(2);
19
+ const all = args.includes("--all");
20
+ const explicit = args.filter((a) => !a.startsWith("--"));
21
+
22
+ // Curated "this is the stack" list โ€” only those actually present get checked.
23
+ const CURATED = new Set([
24
+ "react", "react-dom", "vite", "@vitejs/plugin-react",
25
+ "hono", "@hono/node-server", "zod",
26
+ "tailwindcss", "typescript", "turbo", "vitest",
27
+ "eslint", "prettier",
28
+ "drizzle-orm", "drizzle-kit", "postgres", "mysql2", "better-sqlite3",
29
+ "zustand",
30
+ ]);
31
+
32
+ const deps = collectDeps();
33
+ const names = explicit.length
34
+ ? explicit
35
+ : Object.keys(deps).filter((n) => all || CURATED.has(n));
36
+
37
+ if (names.length === 0) {
38
+ console.log("No matching dependencies found in package.json. Try --all.");
39
+ process.exit(0);
40
+ }
41
+
42
+ console.log("Checking latest stable versions on the npm registryโ€ฆ\n");
43
+ const rows = [];
44
+ let behind = 0;
45
+ for (const name of names.sort()) {
46
+ const current = deps[name] || "(not installed)";
47
+ let latest = "?";
48
+ try {
49
+ latest = execFileSync("npm", ["view", name, "version"], {
50
+ encoding: "utf8",
51
+ stdio: ["ignore", "pipe", "ignore"],
52
+ }).trim();
53
+ } catch {
54
+ latest = "(lookup failed)";
55
+ }
56
+ const state = classify(current, latest);
57
+ if (state.behind) behind++;
58
+ rows.push({ name, current, latest, label: state.label });
59
+ }
60
+
61
+ const w = Math.max(...rows.map((r) => r.name.length), 7);
62
+ console.log(`${"package".padEnd(w)} ${"pinned".padEnd(12)} ${"latest".padEnd(12)} status`);
63
+ console.log("โ”€".repeat(w + 40));
64
+ for (const r of rows) {
65
+ console.log(
66
+ `${r.name.padEnd(w)} ${r.current.padEnd(12)} ${r.latest.padEnd(12)} ${r.label}`
67
+ );
68
+ }
69
+
70
+ console.log(
71
+ behind
72
+ ? `\n${behind} package(s) behind. Plan bumps as a dedicated workstream and update docs/STACK_MAP.md.`
73
+ : "\nโœ“ All checked packages are on the latest stable major."
74
+ );
75
+ process.exit(behind ? 1 : 0);
76
+
77
+ // ---------- helpers ----------
78
+ function collectDeps() {
79
+ const files = ["package.json"];
80
+ for (const dir of ["apps", "packages"]) {
81
+ if (fs.existsSync(dir)) {
82
+ for (const sub of fs.readdirSync(dir)) {
83
+ const p = path.join(dir, sub, "package.json");
84
+ if (fs.existsSync(p)) files.push(p);
85
+ }
86
+ }
87
+ }
88
+ const out = {};
89
+ for (const f of files) {
90
+ if (!fs.existsSync(f)) continue;
91
+ const pkg = JSON.parse(fs.readFileSync(f, "utf8"));
92
+ Object.assign(out, pkg.dependencies, pkg.devDependencies);
93
+ }
94
+ return out;
95
+ }
96
+
97
+ function major(v) {
98
+ const m = String(v).replace(/^[\^~>=<\s]+/, "").match(/^(\d+)/);
99
+ return m ? Number(m[1]) : null;
100
+ }
101
+
102
+ function classify(current, latest) {
103
+ const cM = major(current);
104
+ const lM = major(latest);
105
+ if (cM === null || lM === null) return { behind: false, label: "ยท" };
106
+ if (cM < lM) return { behind: true, label: `โฌ† ${lM - cM} major behind` };
107
+ if (cM > lM) return { behind: false, label: "ahead?" };
108
+ // same major โ€” compare full for a soft hint
109
+ const clean = (v) => String(v).replace(/^[\^~>=<\s]+/, "");
110
+ return clean(current) === latest
111
+ ? { behind: false, label: "โœ“ latest" }
112
+ : { behind: false, label: "โœ“ current major (minor/patch available)" };
113
+ }
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * phase-status โ€” deterministic phase progress report.
4
+ * Shipped into a Groundwork project at docs/.groundwork/scripts/.
5
+ * Run on demand (or via `groundwork status`) so percentages are computed, not guessed.
6
+ *
7
+ * Usage:
8
+ * node phase-status.mjs # all phases
9
+ * node phase-status.mjs 2 # phase 2 only
10
+ */
11
+ import fs from "node:fs";
12
+ import path from "node:path";
13
+
14
+ const arg = process.argv[2];
15
+ const root = "docs/phases";
16
+ if (!fs.existsSync(root)) {
17
+ console.error("โœ— docs/phases not found. Run /plan-phase first.");
18
+ process.exit(1);
19
+ }
20
+
21
+ const files = [];
22
+ (function walk(d) {
23
+ for (const e of fs.readdirSync(d, { withFileTypes: true })) {
24
+ const p = path.join(d, e.name);
25
+ if (e.isDirectory()) walk(p);
26
+ else if (/PHASE.*TASKS\.md$/i.test(e.name)) files.push(p);
27
+ }
28
+ })(root);
29
+
30
+ const filtered = arg
31
+ ? files.filter((f) => new RegExp(`phase0*${arg}\\b`, "i").test(f))
32
+ : files;
33
+
34
+ if (filtered.length === 0) {
35
+ console.error(arg ? `โœ— No tasks file for phase ${arg}.` : "โœ— No PHASE*_TASKS.md files.");
36
+ process.exit(1);
37
+ }
38
+
39
+ let gDone = 0,
40
+ gTotal = 0;
41
+ for (const f of filtered.sort()) {
42
+ const text = fs.readFileSync(f, "utf8");
43
+ const { done, total } = count(text);
44
+ gDone += done;
45
+ gTotal += total;
46
+ const pct = total ? Math.round((done / total) * 100) : 0;
47
+ const label = f.split(path.sep).slice(-2, -1)[0] || path.basename(f);
48
+ console.log(`${label} ${bar(pct)} ${String(pct).padStart(3)}% (${done}/${total})`);
49
+ }
50
+
51
+ if (filtered.length > 1) {
52
+ const pct = gTotal ? Math.round((gDone / gTotal) * 100) : 0;
53
+ console.log("โ”€".repeat(40));
54
+ console.log(`TOTAL ${bar(pct)} ${String(pct).padStart(3)}% (${gDone}/${gTotal})`);
55
+ }
56
+
57
+ function count(text) {
58
+ let done = 0,
59
+ total = 0;
60
+ for (const l of text.split("\n")) {
61
+ if (/^\s*-\s*\[[xX]\]/.test(l)) (done++, total++);
62
+ else if (/^\s*-\s*\[ \]/.test(l)) total++;
63
+ }
64
+ return { done, total };
65
+ }
66
+ function bar(pct, w = 20) {
67
+ const f = Math.round((pct / 100) * w);
68
+ return "โ–ˆ".repeat(f) + "โ–‘".repeat(Math.max(0, w - f));
69
+ }
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * set-fact.mjs โ€” deterministic upsert into docs/FACTS.md.
4
+ *
5
+ * Usage:
6
+ * node docs/.groundwork/scripts/set-fact.mjs <fact-id> "<claim>" --by <agent> --method "<how>"
7
+ *
8
+ * Writes (or replaces, matched by id) one entry in the pinned FACTS grammar:
9
+ * - `fact-id` โ€” claim
10
+ * verified: YYYY-MM-DDTHH:MMZ ยท by: agent ยท method: how
11
+ *
12
+ * Timestamp is stamped here (UTC, minute precision) so agents can't fabricate or forget it.
13
+ * Atomic write: temp file + rename. Exits non-zero on malformed input or missing FACTS.md.
14
+ */
15
+ import fs from "node:fs";
16
+ import path from "node:path";
17
+
18
+ const args = process.argv.slice(2);
19
+ const flags = {};
20
+ const positional = [];
21
+ for (let i = 0; i < args.length; i++) {
22
+ if (args[i] === "--by" || args[i] === "--method" || args[i] === "--file") {
23
+ flags[args[i].slice(2)] = args[++i];
24
+ } else positional.push(args[i]);
25
+ }
26
+ const [id, claim] = positional;
27
+
28
+ const usage = () => {
29
+ console.error(
30
+ 'usage: set-fact.mjs <fact-id> "<claim>" --by <agent> --method "<how>" [--file docs/FACTS.md]'
31
+ );
32
+ process.exit(1);
33
+ };
34
+ if (!id || !claim || !flags.by || !flags.method) usage();
35
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(id)) {
36
+ console.error(`fact id must be kebab-case (got \`${id}\`)`);
37
+ process.exit(1);
38
+ }
39
+ if (/\n/.test(claim) || /\n/.test(flags.method)) {
40
+ console.error("claim and method must be single-line");
41
+ process.exit(1);
42
+ }
43
+
44
+ const file = path.resolve(flags.file || "docs/FACTS.md");
45
+ if (!fs.existsSync(file)) {
46
+ console.error(`${file} not found โ€” run \`groundwork init\`/\`update --docs\` first`);
47
+ process.exit(1);
48
+ }
49
+
50
+ const ts = new Date().toISOString().slice(0, 16) + "Z"; // YYYY-MM-DDTHH:MMZ
51
+ const entry = `- \`${id}\` โ€” ${claim}\n verified: ${ts} ยท by: ${flags.by} ยท method: ${flags.method}`;
52
+
53
+ const text = fs.readFileSync(file, "utf8");
54
+ const lines = text.split("\n");
55
+
56
+ // Locate an existing entry with this id (entry line + its indented meta line, if any).
57
+ const entryRe = new RegExp(`^- \`${id}\` โ€” `);
58
+ const start = lines.findIndex((l) => entryRe.test(l));
59
+ let out;
60
+ if (start >= 0) {
61
+ const hasMeta = /^ {2}verified:/.test(lines[start + 1] || "");
62
+ lines.splice(start, hasMeta ? 2 : 1, ...entry.split("\n"));
63
+ out = lines.join("\n");
64
+ } else {
65
+ const facts = lines.findIndex((l) => /^## Facts\s*$/.test(l));
66
+ if (facts < 0) {
67
+ console.error("no `## Facts` section in FACTS.md โ€” file is malformed");
68
+ process.exit(1);
69
+ }
70
+ // Append after the last existing content of the section (end of file or next heading).
71
+ let end = lines.length;
72
+ for (let i = facts + 1; i < lines.length; i++) {
73
+ if (/^## /.test(lines[i])) {
74
+ end = i;
75
+ break;
76
+ }
77
+ }
78
+ while (end > facts + 1 && lines[end - 1].trim() === "") end--;
79
+ lines.splice(end, 0, "", ...entry.split("\n"));
80
+ out = lines.join("\n");
81
+ }
82
+
83
+ const tmp = `${file}.${process.pid}.tmp`;
84
+ fs.writeFileSync(tmp, out);
85
+ fs.renameSync(tmp, file);
86
+ console.log(`${start >= 0 ? "updated" : "added"} \`${id}\` (verified: ${ts})`);