@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.
- package/LICENSE +21 -0
- package/README.md +323 -0
- package/docs/DECISIONS.md +170 -0
- package/package.json +38 -0
- package/payload/doc-templates/COMMANDS.md +419 -0
- package/payload/doc-templates/DECISIONS.md +168 -0
- package/payload/doc-templates/FACTS.md +43 -0
- package/payload/doc-templates/GROUNDWORK_METHODOLOGY.md +1300 -0
- package/payload/doc-templates/STACK_MAP.md +90 -0
- package/payload/doc-templates/WORKSTREAMS.md +79 -0
- package/payload/doc-templates/_INDEX.md +54 -0
- package/payload/doc-templates/phases/README.md +36 -0
- package/payload/doc-templates/phases/templates/README.md +63 -0
- package/payload/doc-templates/phases/templates/TASK_TEMPLATE.md +302 -0
- package/payload/doc-templates/phases/templates/task_template_prompt.md +229 -0
- package/payload/doc-templates/templates/ARCHITECTURE_GUIDE_TEMPLATE.md +250 -0
- package/payload/doc-templates/templates/DESIGN_SYSTEM_TEMPLATE.md +336 -0
- package/payload/doc-templates/templates/DONE_TEMPLATE.md +21 -0
- package/payload/doc-templates/templates/PHASES_README_TEMPLATE.md +144 -0
- package/payload/doc-templates/templates/PHASE_README_TEMPLATE.md +142 -0
- package/payload/doc-templates/templates/PRD_TEMPLATE.md +348 -0
- package/payload/doc-templates/templates/PRODUCTION_ROADMAP_TEMPLATE.md +168 -0
- package/payload/doc-templates/templates/QUEUE_TEMPLATE.md +17 -0
- package/payload/doc-templates/templates/TECH_STACK_TEMPLATE.md +199 -0
- package/payload/scripts/check-task.mjs +98 -0
- package/payload/scripts/check-versions.mjs +113 -0
- package/payload/scripts/phase-status.mjs +69 -0
- package/payload/scripts/set-fact.mjs +86 -0
- package/payload/skills/add-data-layer/SKILL.md +129 -0
- package/payload/skills/check-task/SKILL.md +35 -0
- package/payload/skills/check-versions/SKILL.md +47 -0
- package/payload/skills/create-prd/SKILL.md +90 -0
- package/payload/skills/domain-model/SKILL.md +90 -0
- package/payload/skills/kickstart/SKILL.md +157 -0
- package/payload/skills/log-decision/SKILL.md +65 -0
- package/payload/skills/next/SKILL.md +65 -0
- package/payload/skills/plan-phase/SKILL.md +108 -0
- package/payload/skills/remember/SKILL.md +77 -0
- package/payload/skills/start-session/SKILL.md +52 -0
- package/payload/skills/update-workstreams/SKILL.md +60 -0
- package/src/cli.mjs +115 -0
- package/src/commands/add.mjs +39 -0
- package/src/commands/artifacts.mjs +24 -0
- package/src/commands/doctor.mjs +292 -0
- package/src/commands/init.mjs +147 -0
- package/src/commands/knowledge.mjs +148 -0
- package/src/commands/list.mjs +61 -0
- package/src/commands/status.mjs +96 -0
- package/src/commands/update.mjs +128 -0
- package/src/lib/adr-tripwire.mjs +171 -0
- package/src/lib/artifacts.mjs +124 -0
- package/src/lib/config.mjs +43 -0
- package/src/lib/fs.mjs +46 -0
- package/src/lib/log.mjs +22 -0
- package/src/lib/paths.mjs +36 -0
- package/src/lib/progress.mjs +26 -0
- 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})`);
|