hardness 1.0.0 → 1.1.2
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/AGENTS.md +11 -0
- package/CHANGELOG.md +36 -0
- package/README.md +62 -15
- package/node_modules/@hardness/analyzers/package.json +1 -1
- package/node_modules/@hardness/core/dist/common/paths.js +2 -2
- package/node_modules/@hardness/core/dist/common/paths.js.map +1 -1
- package/node_modules/@hardness/core/package.json +1 -1
- package/node_modules/@hardness/prompts/package.json +1 -1
- package/package.json +1 -1
- package/packages/analyzers/package.json +1 -1
- package/packages/cli/dist/commands/discover.js +47 -6
- package/packages/cli/dist/commands/discover.js.map +1 -1
- package/packages/cli/dist/commands/plan.js +39 -6
- package/packages/cli/dist/commands/plan.js.map +1 -1
- package/packages/cli/dist/commands/spec.js +34 -6
- package/packages/cli/dist/commands/spec.js.map +1 -1
- package/packages/cli/dist/dispatcher.d.ts +2 -0
- package/packages/cli/dist/dispatcher.js +63 -62
- package/packages/cli/dist/dispatcher.js.map +1 -1
- package/packages/cli/dist/generators/prd-generator.d.ts +14 -0
- package/packages/cli/dist/generators/prd-generator.js +164 -0
- package/packages/cli/dist/generators/prd-generator.js.map +1 -0
- package/packages/cli/dist/generators/spec-generator.d.ts +35 -0
- package/packages/cli/dist/generators/spec-generator.js +245 -0
- package/packages/cli/dist/generators/spec-generator.js.map +1 -0
- package/packages/cli/dist/generators/sprint-generator.d.ts +51 -0
- package/packages/cli/dist/generators/sprint-generator.js +162 -0
- package/packages/cli/dist/generators/sprint-generator.js.map +1 -0
- package/packages/cli/dist/index.js +1 -1
- package/packages/cli/dist/interview/evaluator-prompt.d.ts +9 -0
- package/packages/cli/dist/interview/evaluator-prompt.js +192 -0
- package/packages/cli/dist/interview/evaluator-prompt.js.map +1 -0
- package/packages/cli/dist/interview/evaluator.d.ts +46 -0
- package/packages/cli/dist/interview/evaluator.js +142 -0
- package/packages/cli/dist/interview/evaluator.js.map +1 -0
- package/packages/cli/dist/interview/questions.d.ts +29 -0
- package/packages/cli/dist/interview/questions.js +642 -0
- package/packages/cli/dist/interview/questions.js.map +1 -0
- package/packages/cli/dist/interview/runner.d.ts +14 -0
- package/packages/cli/dist/interview/runner.js +327 -0
- package/packages/cli/dist/interview/runner.js.map +1 -0
- package/packages/cli/dist/interview/suggestions.d.ts +6 -0
- package/packages/cli/dist/interview/suggestions.js +230 -0
- package/packages/cli/dist/interview/suggestions.js.map +1 -0
- package/packages/cli/dist/interview/types.d.ts +46 -0
- package/packages/cli/dist/interview/types.js +50 -0
- package/packages/cli/dist/interview/types.js.map +1 -0
- package/packages/cli/package.json +1 -1
- package/packages/core/dist/common/paths.js +2 -2
- package/packages/core/dist/common/paths.js.map +1 -1
- package/packages/core/package.json +1 -1
- package/packages/prompts/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -60,6 +60,17 @@ Read it carefully. Your `agentCommand` receives the **path** to this file via `{
|
|
|
60
60
|
claude -p "$(cat {context_file})"
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
+
### About `specExcerpt`
|
|
64
|
+
|
|
65
|
+
You do **not** receive the entire `SPEC.md` — only the line ranges referenced by `feature.specLines`. These ranges are selected by the sprint author to give you:
|
|
66
|
+
- The specific requirement(s) you must implement (from section 7 of the SPEC)
|
|
67
|
+
- The relevant module table (section 3) showing where your code lives
|
|
68
|
+
- The relevant data model (section 4) showing the types/interfaces to use
|
|
69
|
+
- The relevant flow (section 5) showing how your feature fits the pipeline
|
|
70
|
+
- Integration contracts (section 6) showing templates, schemas and output paths
|
|
71
|
+
|
|
72
|
+
If you feel you need more context from `SPEC.md` than what `specExcerpt` provides, do **not** read `SPEC.md` directly — instead, note the gap in your output and the sprint author will adjust `specLines` on the next iteration.
|
|
73
|
+
|
|
63
74
|
## Gates you will be measured against
|
|
64
75
|
|
|
65
76
|
The orchestrator runs these gates after you return. You don't invoke them — but you must produce code that passes them.
|
package/CHANGELOG.md
CHANGED
|
@@ -5,8 +5,44 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.1.1] — 2026-07-05
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Automated versioning**: added `scripts/bump-version.js` supporting `major`, `minor`, and `patch` bumps.
|
|
12
|
+
- **Agent workspace rules**: added `.agents/AGENTS.md` to enforce version bumps, builds, and tags before committing.
|
|
13
|
+
- **CI/CD Release workflow**: configured GitHub Action `.github/workflows/release.yml` to compile, test, and release to GitHub/NPM on tag push.
|
|
14
|
+
- **Changelog extraction formatting**: Skip `###` subheaders and clean consecutive newlines in extracted release notes to match the user-friendly format of previous releases.
|
|
15
|
+
|
|
16
|
+
## [1.1.0] — 2026-07-05
|
|
17
|
+
|
|
18
|
+
### Added — Milestone 4: Automated Pipeline
|
|
19
|
+
|
|
20
|
+
- **`hardness discover`**: fully interactive 6-phase PRD interview (Vision → People → Features → Constraints → Boundaries → Review). Deterministic — no LLM required. Suggestions adapt to detected stack and project type. Ctrl+C saves partial session to `.hardness/discover-session.json`.
|
|
21
|
+
- **Interview engine** (`packages/cli/src/interview/`):
|
|
22
|
+
- `types.ts`: `InterviewState`, `ProjectType`, `StackDetection`, `SuggestionEngine` interfaces. `detectStack()` extracted as a shared utility.
|
|
23
|
+
- `suggestions.ts`: static suggestion engine for all 7 project types (web-app, api, cli, library, desktop, mobile, other) with stack-aware deployment overrides.
|
|
24
|
+
- `questions.ts`: 22 questions across 6 phases (Vision=4, People=3, Features=3, Constraints=8, Boundaries=3, Review=2). `LineQueue`-based runner for reliable test isolation.
|
|
25
|
+
- `runner.ts`: readline loop with `?`-for-suggestion, empty-input defaults, multiline support, and graceful SIGINT handling.
|
|
26
|
+
- **`hardness spec`**: reads `PRD.md`, parses all 7 sections, maps functional requirements to technical requirements, renders `SPEC.md` from template. Stack-aware test framework detection (Vitest / pytest / go test / cargo test).
|
|
27
|
+
- **SPEC generator** (`packages/cli/src/generators/spec-generator.ts`): `parsePrd()` + `generateSpec()` with full PRD section parsing.
|
|
28
|
+
- **`hardness plan`**: reads `SPEC.md`, parses section 7 technical requirements, groups into sprints of 2-4 features, derives `files`, `acceptanceCriteria`, `hints`, `verification` per feature, writes `NN-name.json` + `00-index.json` + updates `current.txt`.
|
|
29
|
+
- **Sprint generator** (`packages/cli/src/generators/sprint-generator.ts`): `parseSpec()` + `generateSprints()` with dryRun mode.
|
|
30
|
+
- **PRD generator** (`packages/cli/src/generators/prd-generator.ts`): renders structured `PRD.md` from `InterviewState` following `PRD-TEMPLATE.md`.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- **Dispatcher auto-trigger** (`dispatcher.ts`): bare `hardness` now auto-routes: empty dir → `discover`, `PRD.md` but no `SPEC.md` → `spec`, `SPEC.md` but no sprints → `plan`. Existing Hardness projects continue to show status.
|
|
35
|
+
- **`dispatcher.ts`**: `detectStack()` and `StackDetection` extracted to `interview/types.ts` for sharing across modules.
|
|
36
|
+
- **README.md** + 8 language translations: removed all "(planned, M4)" labels from pipeline table and status callouts. `discover`, `spec`, `plan` are now fully implemented.
|
|
37
|
+
|
|
38
|
+
### Tests
|
|
39
|
+
|
|
40
|
+
- Added 29 new tests across: `runner.test.ts`, `prd-generator.test.ts`, `spec-generator.test.ts`, `sprint-generator.test.ts`, `dispatcher.test.ts` (M4 scenarios), `index.test.ts` (stub removal verification).
|
|
41
|
+
- Total: **126 tests** passing (up from 84 at 1.0.0).
|
|
42
|
+
|
|
8
43
|
## [1.0.0] — 2026-07-04
|
|
9
44
|
|
|
45
|
+
|
|
10
46
|
### Added
|
|
11
47
|
- Initial public release of Hardness — Universal Agentic Development Harness.
|
|
12
48
|
- **CLI** (`hardness`): `init`, `validate`, `score`, `status`, `run`, `audit`, `dry-run`, plus stubs for `discover`, `spec`, `plan` (M4).
|
package/README.md
CHANGED
|
@@ -49,19 +49,45 @@ hardness init --preset manual
|
|
|
49
49
|
|
|
50
50
|
## Quick start
|
|
51
51
|
|
|
52
|
+
The intended flow is **fully guided** — you only need to describe your idea, and Hardness takes care of the rest:
|
|
53
|
+
|
|
52
54
|
```bash
|
|
53
|
-
# 1.
|
|
54
|
-
npx hardness@latest
|
|
55
|
+
# 1. Run Hardness in an empty directory
|
|
56
|
+
npx hardness@latest
|
|
57
|
+
|
|
58
|
+
# 2. Hardness interviews you (interactive PRD questionnaire)
|
|
59
|
+
# — you answer questions about your project
|
|
60
|
+
# — when you don't know, Hardness suggests best practices for your stack
|
|
61
|
+
# — produces PRD.md with your business rules
|
|
62
|
+
|
|
63
|
+
# 3. Hardness generates everything automatically:
|
|
64
|
+
# PRD.md → SPEC.md → sprint JSONs → .hardness/current.txt
|
|
65
|
+
|
|
66
|
+
# 4. Run the loop — the agent implements, gates validate, features advance
|
|
67
|
+
npx hardness@latest run
|
|
68
|
+
|
|
69
|
+
# 5. Final static-analysis audit
|
|
70
|
+
npx hardness@latest audit
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
> Running `hardness` in an empty directory launches the interactive PRD interview; with a `PRD.md` it runs `spec`; with a `SPEC.md` it runs `plan`. You can also use `init`, `validate`, `run` and `audit` with hand-written sprints (see `templates/`).
|
|
74
|
+
|
|
75
|
+
### Already have a project?
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx hardness@latest # detects your stack and suggests an adequacy plan
|
|
79
|
+
npx hardness@latest init --preset claude-code
|
|
80
|
+
npx hardness@latest run
|
|
81
|
+
npx hardness@latest audit
|
|
82
|
+
```
|
|
55
83
|
|
|
56
|
-
|
|
57
|
-
# 3. Point .hardness/current.txt to the first sprint
|
|
84
|
+
### Optional checks along the way
|
|
58
85
|
|
|
59
|
-
|
|
60
|
-
npx hardness
|
|
61
|
-
npx hardness
|
|
62
|
-
npx hardness
|
|
63
|
-
npx hardness run
|
|
64
|
-
npx hardness audit # final static-analysis report
|
|
86
|
+
```bash
|
|
87
|
+
npx hardness@latest validate # check sprint structure
|
|
88
|
+
npx hardness@latest score # quality score 0–10
|
|
89
|
+
npx hardness@latest status # where am I?
|
|
90
|
+
npx hardness@latest dry-run # simulate without invoking the agent
|
|
65
91
|
```
|
|
66
92
|
|
|
67
93
|
## The 6-phase pipeline
|
|
@@ -72,14 +98,14 @@ npx hardness audit # final static-analysis report
|
|
|
72
98
|
|
|
73
99
|
| Phase | Command | What happens |
|
|
74
100
|
|---|---|---|
|
|
75
|
-
| Discover | `hardness discover` | Interview → `PRD.md`
|
|
76
|
-
| Spec | `hardness spec` | `PRD.md` → `SPEC.md`
|
|
77
|
-
| Plan | `hardness plan` | `SPEC.md` → sprint JSON files
|
|
101
|
+
| Discover | `hardness discover` | Interview → `PRD.md` |
|
|
102
|
+
| Spec | `hardness spec` | `PRD.md` → `SPEC.md` |
|
|
103
|
+
| Plan | `hardness plan` | `SPEC.md` → sprint JSON files |
|
|
78
104
|
| Validate | `hardness validate` | Mechanical structural validation of sprints |
|
|
79
105
|
| Run | `hardness run` | Orchestrator loop: agent → gates → advance |
|
|
80
106
|
| Audit | `hardness audit` | Static analysis per stack profile, Markdown + JSON report |
|
|
81
107
|
|
|
82
|
-
|
|
108
|
+
All commands are fully implemented. `discover` runs an interactive PRD interview, `spec` converts PRD.md to SPEC.md, and `plan` generates sprint JSON files. You can also write `SPEC.md` and sprint JSONs by hand using the templates in `templates/`.
|
|
83
109
|
|
|
84
110
|
## Commands
|
|
85
111
|
|
|
@@ -89,7 +115,7 @@ Running `hardness` with **no arguments** triggers an intelligent dispatcher:
|
|
|
89
115
|
2. **Existing project detected** (a `package.json`, `go.mod`, `Cargo.toml`, `pyproject.toml` or source files are present) — presents an **adequacy plan**: detected stack, recommended `stackProfile`, and the sequence `init → validate → run → audit`.
|
|
90
116
|
3. **Empty / new directory** — launches the **PRD interview** (`discover`), an interactive questionnaire that captures business rules and produces `PRD.md`, then proceeds automatically to `spec` (PRD→SPEC), `plan` (SPEC→sprints), `validate`, `run` and `audit`.
|
|
91
117
|
|
|
92
|
-
|
|
118
|
+
|
|
93
119
|
|
|
94
120
|
| Command | Description |
|
|
95
121
|
|---|---|
|
|
@@ -264,6 +290,27 @@ npm test
|
|
|
264
290
|
|
|
265
291
|
See [`CONTRIBUTING.md`](docs/CONTRIBUTING.md) for details.
|
|
266
292
|
|
|
293
|
+
## Acknowledgements
|
|
294
|
+
|
|
295
|
+
This project exists thanks to people and projects who helped shape it, directly or indirectly:
|
|
296
|
+
|
|
297
|
+
- **Bruno Galego** — [augustogalego.com](https://www.augustogalego.com/)
|
|
298
|
+
- **Breno Vieira** — [lionlabs.com.br](https://www.lionlabs.com.br/)
|
|
299
|
+
- **OpenSpec** — [openspec.dev](https://openspec.dev/)
|
|
300
|
+
- **Sandeco** — [github.com/sandeco](https://github.com/sandeco)
|
|
301
|
+
- **Spec-Kit (GitHub Spec Kit)** — [github.com/github/spec-kit](https://github.com/github/spec-kit)
|
|
302
|
+
- **Waldemar Neto** — [techleads.club](https://www.techleads.club/)
|
|
303
|
+
- **My wife** — for the patience, support and countless cans of Coke Zero
|
|
304
|
+
- **The cold of my city** — for keeping me indoors and coding
|
|
305
|
+
|
|
306
|
+
## Contributing
|
|
307
|
+
|
|
308
|
+
Found a bug or have a suggestion? We'd love to hear from you:
|
|
309
|
+
|
|
310
|
+
- 🐛 **Report a bug** — [GitHub Issues](https://github.com/cmt-t/HardNess/issues)
|
|
311
|
+
- 💡 **Suggest a feature** — [GitHub Issues](https://github.com/cmt-t/HardNess/issues/new)
|
|
312
|
+
- 🔧 **Submit a pull request** — see [`CONTRIBUTING.md`](docs/CONTRIBUTING.md)
|
|
313
|
+
|
|
267
314
|
## License
|
|
268
315
|
|
|
269
316
|
[MIT](LICENSE) © 2026 cmt-t
|
|
@@ -33,8 +33,8 @@ export function normalizeProjectRel(filePath, customRoot) {
|
|
|
33
33
|
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
34
34
|
throw new Error(`Path "${filePath}" is outside the project root "${root}"`);
|
|
35
35
|
}
|
|
36
|
-
// Normalize path separators to '/'
|
|
37
|
-
return relative.split(path.sep).join('/');
|
|
36
|
+
// Normalize path separators to '/' (handle both \\ and / regardless of OS)
|
|
37
|
+
return relative.split(path.sep).join('/').replace(/\\/g, '/');
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
40
|
* Returns the list of protected paths/directories that the agent cannot modify.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/common/paths.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;QAC9B,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;AACrC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,sBAA8B,EAAE,UAAmB;IACjF,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC;IACtE,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,sBAAsB,CAAC,CAAC;IAEhE,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAEnD,yEAAyE;IACzE,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CAAC,6BAA6B,sBAAsB,0BAA0B,IAAI,GAAG,CAAC,CAAC;IACxG,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAgB,EAAE,UAAmB;IACvE,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC;IACtE,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAEnD,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CAAC,SAAS,QAAQ,kCAAkC,IAAI,GAAG,CAAC,CAAC;IAC9E,CAAC;IAED,
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/common/paths.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,53 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import { logger, getProjectRoot } from '@hardness/core';
|
|
5
|
+
import { runInterview } from '../interview/runner.js';
|
|
6
|
+
import { generatePrd } from '../generators/prd-generator.js';
|
|
2
7
|
export function discoverCommand(program) {
|
|
3
8
|
program
|
|
4
9
|
.command('discover')
|
|
5
|
-
.description('Interactive interview to generate PRD.md
|
|
6
|
-
.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
.description('Interactive interview to generate PRD.md from your project requirements')
|
|
11
|
+
.option('--root <path>', 'Project root directory (defaults to cwd or HARDNESS_ROOT)')
|
|
12
|
+
.action(async (opts) => {
|
|
13
|
+
const root = opts.root ?? getProjectRoot();
|
|
14
|
+
// Check if a partial session exists
|
|
15
|
+
const sessionPath = path.join(root, '.hardness', 'discover-session.json');
|
|
16
|
+
if (fs.existsSync(sessionPath)) {
|
|
17
|
+
logger.info('Partial session found at .hardness/discover-session.json');
|
|
18
|
+
logger.info('Resuming from where you left off is not yet supported — starting fresh.');
|
|
19
|
+
}
|
|
20
|
+
// Check if PRD.md already exists
|
|
21
|
+
const prdPath = path.join(root, 'PRD.md');
|
|
22
|
+
if (fs.existsSync(prdPath)) {
|
|
23
|
+
logger.warn('PRD.md already exists. It will be overwritten.');
|
|
24
|
+
}
|
|
25
|
+
process.stdout.write(`\n${pc.bold(pc.green('Hardness — Interactive PRD Interview'))}\n`);
|
|
26
|
+
process.stdout.write(pc.dim('Answer each question to generate your PRD.md.\n' +
|
|
27
|
+
'Type ? for a suggestion, or press Enter to accept the default.\n' +
|
|
28
|
+
'Press Ctrl+C at any time to save and exit.\n\n'));
|
|
29
|
+
let state;
|
|
30
|
+
try {
|
|
31
|
+
state = await runInterview({ root });
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
logger.error(`Interview failed: ${err.message}`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
process.stdout.write('\n');
|
|
38
|
+
logger.info('Generating PRD.md...');
|
|
39
|
+
try {
|
|
40
|
+
generatePrd(state, { outputPath: prdPath });
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
logger.error(`PRD generation failed: ${err.message}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
logger.success(`PRD.md created at: ${prdPath}`);
|
|
47
|
+
logger.info('');
|
|
48
|
+
logger.info('Next steps:');
|
|
49
|
+
logger.info(' • Review PRD.md and adjust as needed');
|
|
50
|
+
logger.info(' • Run `hardness spec` to generate SPEC.md');
|
|
10
51
|
});
|
|
11
52
|
}
|
|
12
53
|
//# sourceMappingURL=discover.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discover.js","sourceRoot":"","sources":["../../src/commands/discover.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"discover.js","sourceRoot":"","sources":["../../src/commands/discover.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAE7D,MAAM,UAAU,eAAe,CAAC,OAAgB;IAC9C,OAAO;SACJ,OAAO,CAAC,UAAU,CAAC;SACnB,WAAW,CAAC,yEAAyE,CAAC;SACtF,MAAM,CAAC,eAAe,EAAE,2DAA2D,CAAC;SACpF,MAAM,CAAC,KAAK,EAAE,IAAuB,EAAE,EAAE;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,cAAc,EAAE,CAAC;QAE3C,oCAAoC;QACpC,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,uBAAuB,CAAC,CAAC;QAC1E,IAAI,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAC;YACxE,MAAM,CAAC,IAAI,CAAC,yEAAyE,CAAC,CAAC;QACzF,CAAC;QAED,iCAAiC;QACjC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC1C,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3B,MAAM,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;QAChE,CAAC;QAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC,IAAI,CAAC,CAAC;QACzF,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,EAAE,CAAC,GAAG,CACJ,iDAAiD;YACjD,kEAAkE;YAClE,gDAAgD,CACjD,CACF,CAAC;QAEF,IAAI,KAAK,CAAC;QACV,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,YAAY,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QACvC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,qBAAsB,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;YAC5D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,MAAM,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;QAEpC,IAAI,CAAC;YACH,WAAW,CAAC,KAAK,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;QAC9C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,0BAA2B,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;YACjE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,CAAC,OAAO,CAAC,sBAAsB,OAAO,EAAE,CAAC,CAAC;QAChD,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAChB,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC3B,MAAM,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAC;QACtD,MAAM,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -1,12 +1,45 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { logger, getProjectRoot } from '@hardness/core';
|
|
4
|
+
import { generateSprints } from '../generators/sprint-generator.js';
|
|
2
5
|
export function planCommand(program) {
|
|
3
6
|
program
|
|
4
7
|
.command('plan')
|
|
5
|
-
.description('Consumes SPEC.md and generates
|
|
6
|
-
.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
.description('Consumes SPEC.md and generates sprint JSON files in .hardness/sprints/')
|
|
9
|
+
.option('--root <path>', 'Project root directory (defaults to cwd or HARDNESS_ROOT)')
|
|
10
|
+
.option('--spec <path>', 'Path to SPEC.md (defaults to <root>/SPEC.md)')
|
|
11
|
+
.action(async (opts) => {
|
|
12
|
+
const root = opts.root ?? getProjectRoot();
|
|
13
|
+
const specPath = opts.spec ?? path.join(root, 'SPEC.md');
|
|
14
|
+
const sprintsDir = path.join(root, '.hardness', 'sprints');
|
|
15
|
+
if (!fs.existsSync(specPath)) {
|
|
16
|
+
logger.error(`SPEC.md not found at: ${specPath}`);
|
|
17
|
+
logger.info('Run `hardness spec` first to generate SPEC.md.');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
if (fs.existsSync(sprintsDir) && fs.readdirSync(sprintsDir).length > 0) {
|
|
21
|
+
logger.warn('.hardness/sprints/ already contains files. They will be overwritten.');
|
|
22
|
+
}
|
|
23
|
+
logger.info(`Reading SPEC.md from: ${specPath}`);
|
|
24
|
+
const specContent = fs.readFileSync(specPath, 'utf-8');
|
|
25
|
+
logger.info('Generating sprint files...');
|
|
26
|
+
let result;
|
|
27
|
+
try {
|
|
28
|
+
result = generateSprints(specContent, { root, sprintsDir });
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
logger.error(`Sprint generation failed: ${err.message}`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
logger.success(`Generated ${result.sprints.length} sprint(s) in .hardness/sprints/`);
|
|
35
|
+
result.sprintFiles.forEach((f, i) => {
|
|
36
|
+
logger.info(` Sprint ${i + 1}: ${f} (${result.sprints[i].features.length} features)`);
|
|
37
|
+
});
|
|
38
|
+
logger.info('');
|
|
39
|
+
logger.info('Next steps:');
|
|
40
|
+
logger.info(' • Review .hardness/sprints/ and adjust as needed');
|
|
41
|
+
logger.info(' • Run `hardness validate` to check sprint structure');
|
|
42
|
+
logger.info(' • Run `hardness run` to start executing features');
|
|
10
43
|
});
|
|
11
44
|
}
|
|
12
45
|
//# sourceMappingURL=plan.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/commands/plan.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/commands/plan.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAEpE,MAAM,UAAU,WAAW,CAAC,OAAgB;IAC1C,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,wEAAwE,CAAC;SACrF,MAAM,CAAC,eAAe,EAAE,2DAA2D,CAAC;SACpF,MAAM,CAAC,eAAe,EAAE,8CAA8C,CAAC;SACvE,MAAM,CAAC,KAAK,EAAE,IAAsC,EAAE,EAAE;QACvD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,cAAc,EAAE,CAAC;QAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACzD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC;QAE3D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC7B,MAAM,CAAC,KAAK,CAAC,yBAAyB,QAAQ,EAAE,CAAC,CAAC;YAClD,MAAM,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;YAC9D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvE,MAAM,CAAC,IAAI,CAAC,sEAAsE,CAAC,CAAC;QACtF,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,yBAAyB,QAAQ,EAAE,CAAC,CAAC;QACjD,MAAM,WAAW,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAEvD,MAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;QAE1C,IAAI,MAAM,CAAC;QACX,IAAI,CAAC;YACH,MAAM,GAAG,eAAe,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;QAC9D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,6BAA8B,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;YACpE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,CAAC,OAAO,CAAC,aAAa,MAAM,CAAC,OAAO,CAAC,MAAM,kCAAkC,CAAC,CAAC;QACrF,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YAClC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,YAAY,CAAC,CAAC;QACzF,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAChB,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC3B,MAAM,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;QAClE,MAAM,CAAC,IAAI,CAAC,uDAAuD,CAAC,CAAC;QACrE,MAAM,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -1,12 +1,40 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { logger, getProjectRoot } from '@hardness/core';
|
|
4
|
+
import { generateSpec } from '../generators/spec-generator.js';
|
|
2
5
|
export function specCommand(program) {
|
|
3
6
|
program
|
|
4
7
|
.command('spec')
|
|
5
|
-
.description('Consumes PRD.md and generates SPEC.md
|
|
6
|
-
.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
.description('Consumes PRD.md and generates SPEC.md')
|
|
9
|
+
.option('--root <path>', 'Project root directory (defaults to cwd or HARDNESS_ROOT)')
|
|
10
|
+
.option('--prd <path>', 'Path to PRD.md (defaults to <root>/PRD.md)')
|
|
11
|
+
.action(async (opts) => {
|
|
12
|
+
const root = opts.root ?? getProjectRoot();
|
|
13
|
+
const prdPath = opts.prd ?? path.join(root, 'PRD.md');
|
|
14
|
+
const specPath = path.join(root, 'SPEC.md');
|
|
15
|
+
if (!fs.existsSync(prdPath)) {
|
|
16
|
+
logger.error(`PRD.md not found at: ${prdPath}`);
|
|
17
|
+
logger.info('Run `hardness discover` first to generate PRD.md.');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
if (fs.existsSync(specPath)) {
|
|
21
|
+
logger.warn('SPEC.md already exists. It will be overwritten.');
|
|
22
|
+
}
|
|
23
|
+
logger.info(`Reading PRD.md from: ${prdPath}`);
|
|
24
|
+
const prdContent = fs.readFileSync(prdPath, 'utf-8');
|
|
25
|
+
logger.info('Generating SPEC.md...');
|
|
26
|
+
try {
|
|
27
|
+
generateSpec(prdContent, { outputPath: specPath });
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
logger.error(`SPEC generation failed: ${err.message}`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
logger.success(`SPEC.md created at: ${specPath}`);
|
|
34
|
+
logger.info('');
|
|
35
|
+
logger.info('Next steps:');
|
|
36
|
+
logger.info(' • Review SPEC.md and adjust technical requirements');
|
|
37
|
+
logger.info(' • Run `hardness plan` to generate sprint files');
|
|
10
38
|
});
|
|
11
39
|
}
|
|
12
40
|
//# sourceMappingURL=spec.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spec.js","sourceRoot":"","sources":["../../src/commands/spec.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"spec.js","sourceRoot":"","sources":["../../src/commands/spec.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAE/D,MAAM,UAAU,WAAW,CAAC,OAAgB;IAC1C,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,uCAAuC,CAAC;SACpD,MAAM,CAAC,eAAe,EAAE,2DAA2D,CAAC;SACpF,MAAM,CAAC,cAAc,EAAE,4CAA4C,CAAC;SACpE,MAAM,CAAC,KAAK,EAAE,IAAqC,EAAE,EAAE;QACtD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,cAAc,EAAE,CAAC;QAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAE5C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5B,MAAM,CAAC,KAAK,CAAC,wBAAwB,OAAO,EAAE,CAAC,CAAC;YAChD,MAAM,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;YACjE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;QACjE,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,wBAAwB,OAAO,EAAE,CAAC,CAAC;QAC/C,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAErD,MAAM,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;QAErC,IAAI,CAAC;YACH,YAAY,CAAC,UAAU,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC;QACrD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,2BAA4B,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;YAClE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,CAAC,OAAO,CAAC,uBAAuB,QAAQ,EAAE,CAAC,CAAC;QAClD,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAChB,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC3B,MAAM,CAAC,IAAI,CAAC,sDAAsD,CAAC,CAAC;QACpE,MAAM,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -1,56 +1,30 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { logger, getProjectRoot } from '@hardness/core';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
'pyproject.toml': { stackProfile: 'generic', label: 'Python' },
|
|
10
|
-
'requirements.txt': { stackProfile: 'generic', label: 'Python' },
|
|
11
|
-
'pom.xml': { stackProfile: 'generic', label: 'Java / Maven' },
|
|
12
|
-
'build.gradle': { stackProfile: 'generic', label: 'Java / Gradle' },
|
|
13
|
-
'build.gradle.kts': { stackProfile: 'generic', label: 'Kotlin / Gradle' },
|
|
14
|
-
'Gemfile': { stackProfile: 'generic', label: 'Ruby' },
|
|
15
|
-
'composer.json': { stackProfile: 'generic', label: 'PHP / Composer' },
|
|
16
|
-
'mix.exs': { stackProfile: 'generic', label: 'Elixir' },
|
|
17
|
-
'CMakeLists.txt': { stackProfile: 'generic', label: 'C / C++ (CMake)' },
|
|
18
|
-
'Makefile': { stackProfile: 'generic', label: 'Make' },
|
|
19
|
-
};
|
|
20
|
-
const SOURCE_EXTENSIONS = new Set([
|
|
21
|
-
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
|
|
22
|
-
'.py', '.go', '.rs', '.java', '.kt', '.rb', '.php',
|
|
23
|
-
'.cs', '.cpp', '.c', '.h', '.hpp', '.swift', '.scala',
|
|
24
|
-
'.vue', '.svelte', '.astro',
|
|
25
|
-
]);
|
|
26
|
-
function listTopLevelFiles(dir) {
|
|
27
|
-
try {
|
|
28
|
-
return fs
|
|
29
|
-
.readdirSync(dir, { withFileTypes: true })
|
|
30
|
-
.filter((e) => e.isFile())
|
|
31
|
-
.map((e) => e.name)
|
|
32
|
-
.filter((n) => !n.startsWith('.'));
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
return [];
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
function detectStack(root) {
|
|
39
|
-
const files = listTopLevelFiles(root);
|
|
40
|
-
for (const [manifest, info] of Object.entries(MANIFEST_MAP)) {
|
|
41
|
-
if (files.includes(manifest)) {
|
|
42
|
-
return { manifest, stackProfile: info.stackProfile, label: info.label, files };
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
const hasSource = files.some((f) => SOURCE_EXTENSIONS.has(path.extname(f)));
|
|
46
|
-
if (hasSource) {
|
|
47
|
-
return { manifest: null, stackProfile: 'generic', label: 'Generic (source files detected)', files };
|
|
48
|
-
}
|
|
49
|
-
return { manifest: null, stackProfile: 'generic', label: 'Empty / unknown', files };
|
|
50
|
-
}
|
|
4
|
+
import { detectStack } from './interview/types.js';
|
|
5
|
+
export { detectStack };
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// State detection helpers
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
51
9
|
function hasHardness(root) {
|
|
52
10
|
return fs.existsSync(path.join(root, '.hardness', 'config.json'));
|
|
53
11
|
}
|
|
12
|
+
function hasPrd(root) {
|
|
13
|
+
return fs.existsSync(path.join(root, 'PRD.md'));
|
|
14
|
+
}
|
|
15
|
+
function hasSpec(root) {
|
|
16
|
+
return fs.existsSync(path.join(root, 'SPEC.md'));
|
|
17
|
+
}
|
|
18
|
+
function hasSprints(root) {
|
|
19
|
+
const sprintsDir = path.join(root, '.hardness', 'sprints');
|
|
20
|
+
if (!fs.existsSync(sprintsDir))
|
|
21
|
+
return false;
|
|
22
|
+
const files = fs.readdirSync(sprintsDir).filter((f) => f.endsWith('.json') && f !== '00-index.json');
|
|
23
|
+
return files.length > 0;
|
|
24
|
+
}
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Route handlers
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
54
28
|
function routeExistingHardness(root) {
|
|
55
29
|
const currentPath = path.join(root, '.hardness', 'current.txt');
|
|
56
30
|
let active = '';
|
|
@@ -92,34 +66,61 @@ function routeExistingProject(detection) {
|
|
|
92
66
|
logger.info(' 4. Run: hardness run');
|
|
93
67
|
logger.info(' 5. Audit: hardness audit');
|
|
94
68
|
logger.info('');
|
|
95
|
-
logger.info('Tip: if you do not have a SPEC yet, run `hardness discover`
|
|
69
|
+
logger.info('Tip: if you do not have a SPEC yet, run `hardness discover`');
|
|
96
70
|
logger.info(' to start an interactive PRD interview.');
|
|
97
71
|
}
|
|
98
72
|
function routeEmptyProject() {
|
|
99
73
|
logger.info('No existing project detected in the current directory.');
|
|
100
74
|
logger.info('');
|
|
101
|
-
logger.
|
|
102
|
-
logger.info('');
|
|
103
|
-
logger.info('For now you can:');
|
|
104
|
-
logger.info(' 1. Initialize Hardness manually: hardness init --preset manual');
|
|
105
|
-
logger.info(' 2. Write PRD.md and SPEC.md using templates/ as a guide');
|
|
106
|
-
logger.info(' 3. Create sprint JSONs under .hardness/sprints/');
|
|
107
|
-
logger.info(' 4. Point .hardness/current.txt to the first sprint');
|
|
108
|
-
logger.info(' 5. Run: hardness run');
|
|
109
|
-
logger.info('');
|
|
110
|
-
logger.info('Once `discover` ships (M4), running `hardness` with no arguments in an');
|
|
111
|
-
logger.info('empty directory will start an interactive PRD interview that guides you');
|
|
112
|
-
logger.info('through capturing business rules, then proceeds to SPEC and sprint');
|
|
113
|
-
logger.info('generation automatically.');
|
|
75
|
+
logger.info('Starting interactive PRD interview...');
|
|
76
|
+
logger.info('Run `hardness discover` to begin.');
|
|
114
77
|
}
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Auto-trigger routing (M4)
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
function routeAutoTrigger(root, detection) {
|
|
82
|
+
// PRD exists but no SPEC → spec (takes priority over empty-dir check)
|
|
83
|
+
if (hasPrd(root) && !hasSpec(root)) {
|
|
84
|
+
return 'spec';
|
|
85
|
+
}
|
|
86
|
+
// SPEC exists but no sprints → plan
|
|
87
|
+
if (hasSpec(root) && !hasSprints(root)) {
|
|
88
|
+
return 'plan';
|
|
89
|
+
}
|
|
90
|
+
// Truly empty directory with no docs → discover
|
|
91
|
+
if (detection.label === 'Empty / unknown' && !hasPrd(root) && !hasSpec(root)) {
|
|
92
|
+
return 'discover';
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Main dispatcher
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
115
99
|
export function runDispatcher() {
|
|
116
100
|
const root = getProjectRoot();
|
|
101
|
+
// Existing Hardness project: always show status
|
|
117
102
|
if (hasHardness(root)) {
|
|
118
103
|
routeExistingHardness(root);
|
|
119
104
|
return;
|
|
120
105
|
}
|
|
121
106
|
const detection = detectStack(root);
|
|
122
|
-
|
|
107
|
+
const trigger = routeAutoTrigger(root, detection);
|
|
108
|
+
if (trigger === 'discover') {
|
|
109
|
+
routeEmptyProject();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (trigger === 'spec') {
|
|
113
|
+
logger.info('PRD.md found but no SPEC.md detected.');
|
|
114
|
+
logger.info('Run `hardness spec` to generate SPEC.md from PRD.md.');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (trigger === 'plan') {
|
|
118
|
+
logger.info('SPEC.md found but no sprint files detected.');
|
|
119
|
+
logger.info('Run `hardness plan` to generate sprint files from SPEC.md.');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Existing project (files detected) with no clear M4 trigger
|
|
123
|
+
if (detection.label !== 'Empty / unknown' || hasPrd(root) || hasSpec(root) || hasSprints(root)) {
|
|
123
124
|
routeExistingProject(detection);
|
|
124
125
|
return;
|
|
125
126
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatcher.js","sourceRoot":"","sources":["../src/dispatcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"dispatcher.js","sourceRoot":"","sources":["../src/dispatcher.ts"],"names":[],"mappings":"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"}
|