@seanmars/tospec 0.24.0 → 0.26.0
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/CHANGELOG.md +243 -0
- package/README.md +10 -6
- package/assets/dashboard/lib/derive.js +11 -5
- package/assets/dashboard/lib/store.js +4 -2
- package/assets/dashboard/views/change.js +5 -3
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +42 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +213 -100
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/dashboard.d.ts +9 -0
- package/dist/commands/dashboard.d.ts.map +1 -1
- package/dist/commands/dashboard.js +77 -21
- package/dist/commands/dashboard.js.map +1 -1
- package/dist/commands/decision.d.ts +38 -1
- package/dist/commands/decision.d.ts.map +1 -1
- package/dist/commands/decision.js +303 -27
- package/dist/commands/decision.js.map +1 -1
- package/dist/commands/metrics.d.ts +3 -4
- package/dist/commands/metrics.d.ts.map +1 -1
- package/dist/commands/metrics.js +3 -5
- package/dist/commands/metrics.js.map +1 -1
- package/dist/commands/shared-output.d.ts +24 -0
- package/dist/commands/shared-output.d.ts.map +1 -1
- package/dist/commands/shared-output.js +21 -2
- package/dist/commands/shared-output.js.map +1 -1
- package/dist/commands/show.d.ts.map +1 -1
- package/dist/commands/show.js +12 -9
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/validate.d.ts +19 -0
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +243 -64
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/workflow/instructions.d.ts +7 -1
- package/dist/commands/workflow/instructions.d.ts.map +1 -1
- package/dist/commands/workflow/instructions.js +299 -81
- package/dist/commands/workflow/instructions.js.map +1 -1
- package/dist/commands/workflow/new-change.d.ts.map +1 -1
- package/dist/commands/workflow/new-change.js +26 -17
- package/dist/commands/workflow/new-change.js.map +1 -1
- package/dist/commands/workflow/prompt-escape.d.ts +25 -0
- package/dist/commands/workflow/prompt-escape.d.ts.map +1 -0
- package/dist/commands/workflow/prompt-escape.js +62 -0
- package/dist/commands/workflow/prompt-escape.js.map +1 -0
- package/dist/commands/workflow/schemas.d.ts.map +1 -1
- package/dist/commands/workflow/schemas.js +34 -16
- package/dist/commands/workflow/schemas.js.map +1 -1
- package/dist/commands/workflow/shared.d.ts +46 -10
- package/dist/commands/workflow/shared.d.ts.map +1 -1
- package/dist/commands/workflow/shared.js +66 -30
- package/dist/commands/workflow/shared.js.map +1 -1
- package/dist/commands/workflow/status.d.ts +5 -2
- package/dist/commands/workflow/status.d.ts.map +1 -1
- package/dist/commands/workflow/status.js +62 -28
- package/dist/commands/workflow/status.js.map +1 -1
- package/dist/core/archive.d.ts +38 -0
- package/dist/core/archive.d.ts.map +1 -1
- package/dist/core/archive.js +492 -75
- package/dist/core/archive.js.map +1 -1
- package/dist/core/artifact-graph/index.d.ts +2 -2
- package/dist/core/artifact-graph/index.d.ts.map +1 -1
- package/dist/core/artifact-graph/index.js +2 -2
- package/dist/core/artifact-graph/index.js.map +1 -1
- package/dist/core/artifact-graph/instruction-loader.d.ts +8 -0
- package/dist/core/artifact-graph/instruction-loader.d.ts.map +1 -1
- package/dist/core/artifact-graph/instruction-loader.js +58 -36
- package/dist/core/artifact-graph/instruction-loader.js.map +1 -1
- package/dist/core/artifact-graph/resolver.d.ts +39 -12
- package/dist/core/artifact-graph/resolver.d.ts.map +1 -1
- package/dist/core/artifact-graph/resolver.js +82 -36
- package/dist/core/artifact-graph/resolver.js.map +1 -1
- package/dist/core/artifact-graph/schema.d.ts +24 -0
- package/dist/core/artifact-graph/schema.d.ts.map +1 -1
- package/dist/core/artifact-graph/schema.js +32 -1
- package/dist/core/artifact-graph/schema.js.map +1 -1
- package/dist/core/artifact-graph/stub-detection.d.ts +10 -2
- package/dist/core/artifact-graph/stub-detection.d.ts.map +1 -1
- package/dist/core/artifact-graph/stub-detection.js +18 -11
- package/dist/core/artifact-graph/stub-detection.js.map +1 -1
- package/dist/core/artifact-graph/types.d.ts +2 -2
- package/dist/core/artifact-graph/types.d.ts.map +1 -1
- package/dist/core/artifact-graph/types.js +10 -1
- package/dist/core/artifact-graph/types.js.map +1 -1
- package/dist/core/change-metadata/schema.d.ts.map +1 -1
- package/dist/core/change-metadata/schema.js +18 -4
- package/dist/core/change-metadata/schema.js.map +1 -1
- package/dist/core/change-presenter.d.ts.map +1 -1
- package/dist/core/change-presenter.js +13 -8
- package/dist/core/change-presenter.js.map +1 -1
- package/dist/core/change-status-policy.d.ts.map +1 -1
- package/dist/core/change-status-policy.js +3 -1
- package/dist/core/change-status-policy.js.map +1 -1
- package/dist/core/codex-metrics.d.ts +1 -1
- package/dist/core/codex-metrics.js +1 -1
- package/dist/core/config-schema.d.ts +8 -0
- package/dist/core/config-schema.d.ts.map +1 -1
- package/dist/core/config-schema.js +10 -0
- package/dist/core/config-schema.js.map +1 -1
- package/dist/core/dashboard-activity.d.ts +4 -4
- package/dist/core/dashboard-activity.js +4 -4
- package/dist/core/dashboard-data.d.ts +11 -5
- package/dist/core/dashboard-data.d.ts.map +1 -1
- package/dist/core/dashboard-data.js +54 -28
- package/dist/core/dashboard-data.js.map +1 -1
- package/dist/core/dashboard-hub.d.ts +3 -3
- package/dist/core/dashboard-hub.d.ts.map +1 -1
- package/dist/core/dashboard-hub.js +16 -4
- package/dist/core/dashboard-hub.js.map +1 -1
- package/dist/core/dashboard-routes.d.ts +2 -1
- package/dist/core/dashboard-routes.d.ts.map +1 -1
- package/dist/core/dashboard-routes.js +102 -33
- package/dist/core/dashboard-routes.js.map +1 -1
- package/dist/core/global-config.d.ts +71 -2
- package/dist/core/global-config.d.ts.map +1 -1
- package/dist/core/global-config.js +149 -4
- package/dist/core/global-config.js.map +1 -1
- package/dist/core/id.d.ts +9 -0
- package/dist/core/id.d.ts.map +1 -1
- package/dist/core/id.js +37 -0
- package/dist/core/id.js.map +1 -1
- package/dist/core/init.d.ts +9 -2
- package/dist/core/init.d.ts.map +1 -1
- package/dist/core/init.js +116 -33
- package/dist/core/init.js.map +1 -1
- package/dist/core/list.d.ts.map +1 -1
- package/dist/core/list.js +69 -45
- package/dist/core/list.js.map +1 -1
- package/dist/core/local-server.d.ts +9 -2
- package/dist/core/local-server.d.ts.map +1 -1
- package/dist/core/local-server.js +24 -4
- package/dist/core/local-server.js.map +1 -1
- package/dist/core/markdown-render.d.ts.map +1 -1
- package/dist/core/markdown-render.js +7 -2
- package/dist/core/markdown-render.js.map +1 -1
- package/dist/core/migrate.d.ts +24 -2
- package/dist/core/migrate.d.ts.map +1 -1
- package/dist/core/migrate.js +335 -59
- package/dist/core/migrate.js.map +1 -1
- package/dist/core/parsers/change-parser.d.ts +20 -1
- package/dist/core/parsers/change-parser.d.ts.map +1 -1
- package/dist/core/parsers/change-parser.js +76 -73
- package/dist/core/parsers/change-parser.js.map +1 -1
- package/dist/core/parsers/markdown-parser.d.ts +24 -0
- package/dist/core/parsers/markdown-parser.d.ts.map +1 -1
- package/dist/core/parsers/markdown-parser.js +65 -10
- package/dist/core/parsers/markdown-parser.js.map +1 -1
- package/dist/core/parsers/requirement-blocks.d.ts +127 -6
- package/dist/core/parsers/requirement-blocks.d.ts.map +1 -1
- package/dist/core/parsers/requirement-blocks.js +349 -56
- package/dist/core/parsers/requirement-blocks.js.map +1 -1
- package/dist/core/parsers/requirement-text.d.ts +25 -1
- package/dist/core/parsers/requirement-text.d.ts.map +1 -1
- package/dist/core/parsers/requirement-text.js +62 -6
- package/dist/core/parsers/requirement-text.js.map +1 -1
- package/dist/core/parsers/spec-structure.d.ts.map +1 -1
- package/dist/core/parsers/spec-structure.js +36 -24
- package/dist/core/parsers/spec-structure.js.map +1 -1
- package/dist/core/project-config.d.ts +9 -2
- package/dist/core/project-config.d.ts.map +1 -1
- package/dist/core/project-config.js +30 -7
- package/dist/core/project-config.js.map +1 -1
- package/dist/core/project-layout.d.ts +26 -0
- package/dist/core/project-layout.d.ts.map +1 -1
- package/dist/core/project-layout.js +51 -1
- package/dist/core/project-layout.js.map +1 -1
- package/dist/core/schema-names.d.ts +17 -0
- package/dist/core/schema-names.d.ts.map +1 -1
- package/dist/core/schema-names.js +24 -0
- package/dist/core/schema-names.js.map +1 -1
- package/dist/core/shared/index.d.ts +1 -1
- package/dist/core/shared/index.d.ts.map +1 -1
- package/dist/core/shared/index.js +1 -1
- package/dist/core/shared/index.js.map +1 -1
- package/dist/core/shared/rules-generation.d.ts +12 -0
- package/dist/core/shared/rules-generation.d.ts.map +1 -1
- package/dist/core/shared/rules-generation.js +26 -2
- package/dist/core/shared/rules-generation.js.map +1 -1
- package/dist/core/shared/skill-generation.d.ts +7 -0
- package/dist/core/shared/skill-generation.d.ts.map +1 -1
- package/dist/core/shared/skill-generation.js +19 -0
- package/dist/core/shared/skill-generation.js.map +1 -1
- package/dist/core/shared/tool-detection.d.ts +10 -1
- package/dist/core/shared/tool-detection.d.ts.map +1 -1
- package/dist/core/shared/tool-detection.js +37 -6
- package/dist/core/shared/tool-detection.js.map +1 -1
- package/dist/core/spec-presenter.d.ts.map +1 -1
- package/dist/core/spec-presenter.js +23 -1
- package/dist/core/spec-presenter.js.map +1 -1
- package/dist/core/specs-apply.d.ts +98 -1
- package/dist/core/specs-apply.d.ts.map +1 -1
- package/dist/core/specs-apply.js +426 -63
- package/dist/core/specs-apply.js.map +1 -1
- package/dist/core/templates/fragments/change-diff.d.ts +37 -0
- package/dist/core/templates/fragments/change-diff.d.ts.map +1 -0
- package/dist/core/templates/fragments/change-diff.js +37 -0
- package/dist/core/templates/fragments/change-diff.js.map +1 -0
- package/dist/core/templates/fragments/sync-fail.d.ts +8 -0
- package/dist/core/templates/fragments/sync-fail.d.ts.map +1 -0
- package/dist/core/templates/fragments/sync-fail.js +8 -0
- package/dist/core/templates/fragments/sync-fail.js.map +1 -0
- package/dist/core/templates/fragments/validate.d.ts +14 -1
- package/dist/core/templates/fragments/validate.d.ts.map +1 -1
- package/dist/core/templates/fragments/validate.js +14 -1
- package/dist/core/templates/fragments/validate.js.map +1 -1
- package/dist/core/templates/fragments/verify.d.ts +2 -2
- package/dist/core/templates/fragments/verify.d.ts.map +1 -1
- package/dist/core/templates/fragments/verify.js +8 -5
- package/dist/core/templates/fragments/verify.js.map +1 -1
- package/dist/core/templates/workflows/apply.d.ts +1 -1
- package/dist/core/templates/workflows/apply.d.ts.map +1 -1
- package/dist/core/templates/workflows/apply.js +29 -10
- package/dist/core/templates/workflows/apply.js.map +1 -1
- package/dist/core/templates/workflows/archive.d.ts.map +1 -1
- package/dist/core/templates/workflows/archive.js +27 -9
- package/dist/core/templates/workflows/archive.js.map +1 -1
- package/dist/core/templates/workflows/decision.d.ts.map +1 -1
- package/dist/core/templates/workflows/decision.js +10 -4
- package/dist/core/templates/workflows/decision.js.map +1 -1
- package/dist/core/templates/workflows/explore.d.ts +2 -2
- package/dist/core/templates/workflows/explore.d.ts.map +1 -1
- package/dist/core/templates/workflows/explore.js +7 -3
- package/dist/core/templates/workflows/explore.js.map +1 -1
- package/dist/core/templates/workflows/grill.d.ts +2 -2
- package/dist/core/templates/workflows/grill.d.ts.map +1 -1
- package/dist/core/templates/workflows/grill.js +5 -4
- package/dist/core/templates/workflows/grill.js.map +1 -1
- package/dist/core/templates/workflows/issue.d.ts.map +1 -1
- package/dist/core/templates/workflows/issue.js +9 -3
- package/dist/core/templates/workflows/issue.js.map +1 -1
- package/dist/core/templates/workflows/propose.d.ts.map +1 -1
- package/dist/core/templates/workflows/propose.js +4 -1
- package/dist/core/templates/workflows/propose.js.map +1 -1
- package/dist/core/templates/workflows/sync.d.ts.map +1 -1
- package/dist/core/templates/workflows/sync.js +25 -11
- package/dist/core/templates/workflows/sync.js.map +1 -1
- package/dist/core/templates/workflows/update.d.ts.map +1 -1
- package/dist/core/templates/workflows/update.js +17 -10
- package/dist/core/templates/workflows/update.js.map +1 -1
- package/dist/core/update.d.ts +5 -0
- package/dist/core/update.d.ts.map +1 -1
- package/dist/core/update.js +66 -29
- package/dist/core/update.js.map +1 -1
- package/dist/core/validation/constants.d.ts +7 -1
- package/dist/core/validation/constants.d.ts.map +1 -1
- package/dist/core/validation/constants.js +16 -2
- package/dist/core/validation/constants.js.map +1 -1
- package/dist/core/validation/purpose-placeholder.d.ts.map +1 -1
- package/dist/core/validation/purpose-placeholder.js +35 -11
- package/dist/core/validation/purpose-placeholder.js.map +1 -1
- package/dist/core/validation/section-validator.d.ts +7 -0
- package/dist/core/validation/section-validator.d.ts.map +1 -1
- package/dist/core/validation/section-validator.js +22 -2
- package/dist/core/validation/section-validator.js.map +1 -1
- package/dist/core/validation/sister-changes.d.ts +93 -0
- package/dist/core/validation/sister-changes.d.ts.map +1 -0
- package/dist/core/validation/sister-changes.js +180 -0
- package/dist/core/validation/sister-changes.js.map +1 -0
- package/dist/core/validation/task-markers.d.ts +9 -0
- package/dist/core/validation/task-markers.d.ts.map +1 -0
- package/dist/core/validation/task-markers.js +71 -0
- package/dist/core/validation/task-markers.js.map +1 -0
- package/dist/core/validation/types.d.ts +58 -0
- package/dist/core/validation/types.d.ts.map +1 -1
- package/dist/core/validation/types.js +22 -0
- package/dist/core/validation/types.js.map +1 -1
- package/dist/core/validation/validator.d.ts +79 -2
- package/dist/core/validation/validator.d.ts.map +1 -1
- package/dist/core/validation/validator.js +565 -84
- package/dist/core/validation/validator.js.map +1 -1
- package/dist/utils/change-metadata.d.ts +62 -5
- package/dist/utils/change-metadata.d.ts.map +1 -1
- package/dist/utils/change-metadata.js +193 -31
- package/dist/utils/change-metadata.js.map +1 -1
- package/dist/utils/change-utils.d.ts +21 -0
- package/dist/utils/change-utils.d.ts.map +1 -1
- package/dist/utils/change-utils.js +168 -53
- package/dist/utils/change-utils.js.map +1 -1
- package/dist/utils/command-line.d.ts +11 -0
- package/dist/utils/command-line.d.ts.map +1 -0
- package/dist/utils/command-line.js +43 -0
- package/dist/utils/command-line.js.map +1 -0
- package/dist/utils/display-width.d.ts +10 -0
- package/dist/utils/display-width.d.ts.map +1 -0
- package/dist/utils/display-width.js +15 -0
- package/dist/utils/display-width.js.map +1 -0
- package/dist/utils/file-system.d.ts +16 -0
- package/dist/utils/file-system.d.ts.map +1 -1
- package/dist/utils/file-system.js +49 -2
- package/dist/utils/file-system.js.map +1 -1
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +11 -5
- package/dist/utils/frontmatter.js.map +1 -1
- package/dist/utils/interactive.d.ts.map +1 -1
- package/dist/utils/interactive.js +3 -1
- package/dist/utils/interactive.js.map +1 -1
- package/dist/utils/item-discovery.d.ts +3 -2
- package/dist/utils/item-discovery.d.ts.map +1 -1
- package/dist/utils/item-discovery.js +34 -16
- package/dist/utils/item-discovery.js.map +1 -1
- package/dist/utils/link.d.ts +0 -1
- package/dist/utils/link.d.ts.map +1 -1
- package/dist/utils/link.js +0 -1
- package/dist/utils/link.js.map +1 -1
- package/dist/utils/match.d.ts.map +1 -1
- package/dist/utils/match.js +10 -1
- package/dist/utils/match.js.map +1 -1
- package/dist/utils/option-flags.d.ts +9 -0
- package/dist/utils/option-flags.d.ts.map +1 -0
- package/dist/utils/option-flags.js +12 -0
- package/dist/utils/option-flags.js.map +1 -0
- package/dist/utils/sentence.d.ts +9 -0
- package/dist/utils/sentence.d.ts.map +1 -0
- package/dist/utils/sentence.js +12 -0
- package/dist/utils/sentence.js.map +1 -0
- package/dist/utils/spec-files.d.ts +84 -1
- package/dist/utils/spec-files.d.ts.map +1 -1
- package/dist/utils/spec-files.js +192 -3
- package/dist/utils/spec-files.js.map +1 -1
- package/dist/utils/task-progress.d.ts.map +1 -1
- package/dist/utils/task-progress.js +32 -9
- package/dist/utils/task-progress.js.map +1 -1
- package/dist/utils/timestamp.d.ts +27 -0
- package/dist/utils/timestamp.d.ts.map +1 -1
- package/dist/utils/timestamp.js +37 -0
- package/dist/utils/timestamp.js.map +1 -1
- package/package.json +4 -1
- package/schemas/decision/templates/decision.md +2 -2
- package/schemas/issue/schema.yaml +12 -6
- package/schemas/issue/templates/task.md +1 -1
- package/schemas/sdd/schema.yaml +21 -10
- package/schemas/sdd/templates/proposal.md +1 -1
package/dist/utils/spec-files.js
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { hasDeltaSection } from '../core/parsers/requirement-blocks.js';
|
|
4
|
+
import { FileSystemUtils, enterOnce, isDirectoryEntry } from './file-system.js';
|
|
5
|
+
import { findDeltaSpecSources } from '../core/specs-apply.js';
|
|
3
6
|
/**
|
|
4
7
|
* Absolute paths of every accepted file under `dir`, at any depth, sorted by
|
|
5
8
|
* code point — not localeCompare, which follows the process ICU locale and
|
|
6
9
|
* would order issues differently on CI than locally. A missing or unreadable
|
|
7
10
|
* directory means "no files".
|
|
11
|
+
*
|
|
12
|
+
* Links are followed, as the merge path follows them: a delta the merge reads
|
|
13
|
+
* through a linked capability folder or a linked `spec.md` must be one validate
|
|
14
|
+
* reads too, or validate reports "no deltas" for a change archive merges.
|
|
8
15
|
*/
|
|
9
16
|
async function findFiles(dir, accept) {
|
|
10
17
|
const results = [];
|
|
18
|
+
const visited = new Set();
|
|
11
19
|
const walk = async (current) => {
|
|
20
|
+
if (!(await enterOnce(visited, current)))
|
|
21
|
+
return;
|
|
12
22
|
let entries;
|
|
13
23
|
try {
|
|
14
24
|
entries = await fs.readdir(current, { withFileTypes: true });
|
|
@@ -18,10 +28,10 @@ async function findFiles(dir, accept) {
|
|
|
18
28
|
}
|
|
19
29
|
for (const entry of entries) {
|
|
20
30
|
const full = path.join(current, entry.name);
|
|
21
|
-
if (entry
|
|
31
|
+
if (isDirectoryEntry(current, entry)) {
|
|
22
32
|
await walk(full);
|
|
23
33
|
}
|
|
24
|
-
else if (entry.
|
|
34
|
+
else if (accept(entry.name) && (entry.isFile() || (await isLinkedFile(full, entry)))) {
|
|
25
35
|
results.push(full);
|
|
26
36
|
}
|
|
27
37
|
}
|
|
@@ -29,6 +39,11 @@ async function findFiles(dir, accept) {
|
|
|
29
39
|
await walk(dir);
|
|
30
40
|
return results.sort();
|
|
31
41
|
}
|
|
42
|
+
async function isLinkedFile(full, entry) {
|
|
43
|
+
if (!entry.isSymbolicLink())
|
|
44
|
+
return false;
|
|
45
|
+
return fs.stat(full).then((stat) => stat.isFile(), () => false);
|
|
46
|
+
}
|
|
32
47
|
/**
|
|
33
48
|
* Every `spec.md` under a change's `specs/` directory, at any depth.
|
|
34
49
|
*
|
|
@@ -36,11 +51,67 @@ async function findFiles(dir, accept) {
|
|
|
36
51
|
* `specs/<capability>/spec.md`: the merge path reads one level, so anything
|
|
37
52
|
* deeper would be applied by nobody, and finding it lets the validator report it
|
|
38
53
|
* and archive refuse.
|
|
39
|
-
* tospec/decisions/20260730_014309-delta-spec-layout-one-level.md
|
|
40
54
|
*/
|
|
41
55
|
export async function findSpecFiles(specsDir) {
|
|
42
56
|
return findFiles(specsDir, (name) => name === 'spec.md');
|
|
43
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* The spec files a declared `skip_specs: true` contradicts, relative to
|
|
60
|
+
* `specs/` in POSIX form; none when the marker is not declared. validate's
|
|
61
|
+
* ERROR and `instructions apply`'s warning both read this list, so the two
|
|
62
|
+
* cannot disagree about which change carries the contradiction.
|
|
63
|
+
*
|
|
64
|
+
* Counts only the files the merge would apply, picked by the merge's own
|
|
65
|
+
* selection. A root `specs/<cap>.md` is applied by nobody, so it does not
|
|
66
|
+
* contradict "this change merges no deltas"; validate warns about it instead.
|
|
67
|
+
* Calling it a contradiction would make the same file an ERROR under
|
|
68
|
+
* skip_specs and a WARNING everywhere else, and would block a root notes file
|
|
69
|
+
* that merely quotes a delta header — the case the WARNING level exists for.
|
|
70
|
+
* A nested or root `spec.md` is no contradiction either, for the same reason:
|
|
71
|
+
* validate's layout ERROR names it whatever the marker says, and counted here
|
|
72
|
+
* it was also the one spec file apply's "Not present in this change: specs"
|
|
73
|
+
* told the agent to ignore.
|
|
74
|
+
*/
|
|
75
|
+
export async function findSkipSpecsContradictions(changeDir, marker) {
|
|
76
|
+
if (!marker.declared)
|
|
77
|
+
return [];
|
|
78
|
+
const specsDir = path.join(changeDir, 'specs');
|
|
79
|
+
return (await findDeltaSpecSources(specsDir)).map(({ source }) => FileSystemUtils.toPosixPath(path.relative(specsDir, source)));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Why validate refuses a `.md` file below the `specs/` root by its path alone,
|
|
83
|
+
* or null for `<capability>/spec.md`. `entryPath` is relative to `specs/`, in
|
|
84
|
+
* POSIX form, and at least one folder deep: `specs/spec.md` has its own rule
|
|
85
|
+
* ({@link findRootSpecDelta}) and any other root file only warns
|
|
86
|
+
* ({@link findRootDeltaFiles}). validate's layout ERRORs and
|
|
87
|
+
* {@link findMisplacedSpecFiles} both judge by it, so apply cannot call a change
|
|
88
|
+
* ready that validate refuses for its layout.
|
|
89
|
+
*/
|
|
90
|
+
export function specLayoutFault(entryPath) {
|
|
91
|
+
const segments = entryPath.split('/');
|
|
92
|
+
if (segments.length > 2)
|
|
93
|
+
return 'nested';
|
|
94
|
+
// fast-glob (which computes artifact status) skips dot-directories while the
|
|
95
|
+
// merge path's readdir does not.
|
|
96
|
+
if (segments[0].startsWith('.'))
|
|
97
|
+
return 'dot-folder';
|
|
98
|
+
return segments[1] === 'spec.md' ? null : 'misnamed';
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The files validate refuses for where they sit, relative to `specs/` in POSIX
|
|
102
|
+
* form: `specs/spec.md` by {@link findRootSpecDelta}, and every `.md` below the
|
|
103
|
+
* root by {@link specLayoutFault}. `skip_specs` does not change the list, as it
|
|
104
|
+
* does not change those ERRORs.
|
|
105
|
+
*/
|
|
106
|
+
export async function findMisplacedSpecFiles(specsDir, options) {
|
|
107
|
+
const relative = (file) => FileSystemUtils.toPosixPath(path.relative(specsDir, file));
|
|
108
|
+
const rootSpec = await findRootSpecDelta(specsDir, options);
|
|
109
|
+
const below = (await findAllMarkdownFiles(specsDir))
|
|
110
|
+
.filter((file) => path.dirname(file) !== specsDir)
|
|
111
|
+
.map(relative)
|
|
112
|
+
.filter((entryPath) => specLayoutFault(entryPath) !== null);
|
|
113
|
+
return [...(rootSpec === undefined ? [] : [relative(rootSpec)]), ...below].sort();
|
|
114
|
+
}
|
|
44
115
|
/**
|
|
45
116
|
* Every `*.md` under a change's `specs/` directory, at any depth. Unlike
|
|
46
117
|
* {@link findSpecFiles} the basename is not filtered, so the validator can
|
|
@@ -50,4 +121,122 @@ export async function findSpecFiles(specsDir) {
|
|
|
50
121
|
export async function findAllMarkdownFiles(specsDir) {
|
|
51
122
|
return findFiles(specsDir, (name) => name.toLowerCase().endsWith('.md'));
|
|
52
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* `*.md` files directly under `specs/` (other than the file `specs/spec.md`
|
|
126
|
+
* resolves to, which has its own ERROR) that carry a delta section header — a delta written as
|
|
127
|
+
* `specs/<capability>.md` instead of `specs/<capability>/spec.md`, which the
|
|
128
|
+
* merge never reads.
|
|
129
|
+
*
|
|
130
|
+
* Only these: a root `.md` without a delta header is notes, and stays clutter
|
|
131
|
+
* rather than a lost delta. Validate and archive both read this one list and
|
|
132
|
+
* both only warn, so neither refuses what the other accepts.
|
|
133
|
+
*
|
|
134
|
+
* `includeRootSpec` keeps `specs/spec.md` in the list, for a caller where
|
|
135
|
+
* nothing else names it: archive wherever the merge-empty backstop does not run
|
|
136
|
+
* (under `--skip-specs`, or beside a capability delta).
|
|
137
|
+
*/
|
|
138
|
+
export async function findRootDeltaFiles(specsDir, options = {}) {
|
|
139
|
+
let entries;
|
|
140
|
+
try {
|
|
141
|
+
entries = await fs.readdir(specsDir, { withFileTypes: true });
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
const rootSpecName = options.includeRootSpec
|
|
147
|
+
? undefined
|
|
148
|
+
: await resolveRootSpecName(specsDir, entries.map((entry) => entry.name));
|
|
149
|
+
const found = [];
|
|
150
|
+
for (const entry of entries) {
|
|
151
|
+
if (!entry.isFile() || !entry.name.toLowerCase().endsWith('.md') || entry.name === rootSpecName)
|
|
152
|
+
continue;
|
|
153
|
+
const full = path.join(specsDir, entry.name);
|
|
154
|
+
let content;
|
|
155
|
+
try {
|
|
156
|
+
content = await fs.readFile(full, 'utf-8');
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (hasDeltaSection(content))
|
|
162
|
+
found.push(full);
|
|
163
|
+
}
|
|
164
|
+
return found.sort();
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* `*.md` files below the `specs/` root, other than a `spec.md`, that carry a
|
|
168
|
+
* delta section header: a delta written as `specs/<capability>/notes.md` or
|
|
169
|
+
* `SPEC.md`, which the merge never reads (it reads `spec.md`, spelled exactly).
|
|
170
|
+
* Without a delta header the file is notes, the rule {@link findRootDeltaFiles}
|
|
171
|
+
* applies at the root. Root files are left to that function.
|
|
172
|
+
*/
|
|
173
|
+
export async function findMisnamedDeltaFiles(specsDir) {
|
|
174
|
+
const found = [];
|
|
175
|
+
for (const file of await findAllMarkdownFiles(specsDir)) {
|
|
176
|
+
if (path.dirname(file) === specsDir || path.basename(file) === 'spec.md')
|
|
177
|
+
continue;
|
|
178
|
+
let content;
|
|
179
|
+
try {
|
|
180
|
+
content = await fs.readFile(file, 'utf-8');
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (hasDeltaSection(content))
|
|
186
|
+
found.push(file);
|
|
187
|
+
}
|
|
188
|
+
return found;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* The file the validator's `specs/spec.md` ERROR lands on, under its name on
|
|
192
|
+
* disk, for a caller that names that file without running that check: the
|
|
193
|
+
* archive backstop. {@link findSpecFiles} matches the basename exactly, so on a
|
|
194
|
+
* case-insensitive file system a lone `SPEC.md` was left out of both that
|
|
195
|
+
* backstop and {@link findRootDeltaFiles}, and archived under `--no-validate`
|
|
196
|
+
* with nothing naming it.
|
|
197
|
+
*/
|
|
198
|
+
export async function findRootSpecFile(specsDir) {
|
|
199
|
+
let names;
|
|
200
|
+
try {
|
|
201
|
+
names = await fs.readdir(specsDir);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
const name = await resolveRootSpecName(specsDir, names);
|
|
207
|
+
return name === undefined ? undefined : path.join(specsDir, name);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* The file `specs/spec.md` resolves to, when it is a delta the merge cannot
|
|
211
|
+
* read: always where the change must carry a delta, and elsewhere only with a
|
|
212
|
+
* delta header, since without one it is notes. validate's ERROR and the
|
|
213
|
+
* archive backstop both pick it here: the backstop counted the file whatever
|
|
214
|
+
* the schema demanded, so it refused notes validate had passed and sent the
|
|
215
|
+
* user back to validate.
|
|
216
|
+
*/
|
|
217
|
+
export async function findRootSpecDelta(specsDir, options) {
|
|
218
|
+
const file = await findRootSpecFile(specsDir);
|
|
219
|
+
if (file === undefined || options.requireDelta)
|
|
220
|
+
return file;
|
|
221
|
+
const content = await fs.readFile(file, 'utf-8').catch(() => '');
|
|
222
|
+
return hasDeltaSection(content) ? file : undefined;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* The entry name the validator's `specs/spec.md` ERROR lands on. That check
|
|
226
|
+
* stats the path, so the file system decides the case: on a case-insensitive
|
|
227
|
+
* one (Windows, default macOS) it fires for `SPEC.md` too. A plain
|
|
228
|
+
* case-insensitive exclusion would instead hide a `SPEC.md` delta on a
|
|
229
|
+
* case-sensitive file system, where that ERROR never fires for it.
|
|
230
|
+
*
|
|
231
|
+
* Only a regular file, as in that check: a directory named `spec.md` is a
|
|
232
|
+
* capability folder.
|
|
233
|
+
*/
|
|
234
|
+
async function resolveRootSpecName(specsDir, names) {
|
|
235
|
+
const stat = await fs.stat(path.join(specsDir, 'spec.md')).catch(() => null);
|
|
236
|
+
if (!stat?.isFile())
|
|
237
|
+
return undefined;
|
|
238
|
+
if (names.includes('spec.md'))
|
|
239
|
+
return 'spec.md';
|
|
240
|
+
return names.find((name) => name.toLowerCase() === 'spec.md');
|
|
241
|
+
}
|
|
53
242
|
//# sourceMappingURL=spec-files.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spec-files.js","sourceRoot":"","sources":["../../src/utils/spec-files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"spec-files.js","sourceRoot":"","sources":["../../src/utils/spec-files.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-progress.d.ts","sourceRoot":"","sources":["../../src/utils/task-progress.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"task-progress.d.ts","sourceRoot":"","sources":["../../src/utils/task-progress.ts"],"names":[],"mappings":"AAyCA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,QAAS,SAAQ,UAAU;IAC1C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAoBnE;AA8BD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAOjF;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAarB;AAED,qFAAqF;AACrF,wBAAsB,wBAAwB,CAC5C,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,CAAC,CAGvB;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAwB1F;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,CAI/D"}
|
|
@@ -12,13 +12,30 @@ import { resolveSchemaForChange } from './change-metadata.js';
|
|
|
12
12
|
* GitHub's rendering is the only view of tasks.md the author can check against,
|
|
13
13
|
* so "tasks tospec sees" must equal "tasks GitHub draws". Too narrow and
|
|
14
14
|
* `+ [ ] 1.2 …` is a box nobody can count; too wide and `- [ ]2.6 …` is a task
|
|
15
|
-
* nobody can tick.
|
|
15
|
+
* nobody can tick. Ordered-list markers (`1.` / `1)`, up to 9 digits as in
|
|
16
|
+
* CommonMark) are GFM list items too: left out, `1. [ ] 1.2 …` was a drawn box
|
|
17
|
+
* the archive gate never saw, so an unfinished change passed it.
|
|
16
18
|
*
|
|
17
19
|
* Fences are masked by the callers (`buildCodeFenceMask`), not here: a tasks.md
|
|
18
20
|
* documenting the task format puts real `- [ ]` lines in a fence, which would
|
|
19
21
|
* leave the archive gate permanently incomplete.
|
|
22
|
+
*
|
|
23
|
+
* The text is `[^\r\n]*`, not `.*`: `.` also stops at U+2028/U+2029, but
|
|
24
|
+
* CommonMark ends a line only at LF, CR or CRLF, so GitHub draws
|
|
25
|
+
* `- [ ] a<U+2028>b` as a checkbox. Under `.*` that box went uncounted and an
|
|
26
|
+
* unfinished change passed the archive gate.
|
|
27
|
+
*
|
|
28
|
+
* `(?![ \t])` stops the text from re-splitting the blanks after `]`. Without it,
|
|
29
|
+
* on a line the text cannot finish, `[ \t]+` gave its blanks back one at a time
|
|
30
|
+
* and the text rescanned to the terminator after each: `- [ ]` + 80k blanks +
|
|
31
|
+
* U+2028 took 7.5 s under `.*`, and a bare CR that setTaskDone's old `\n` split
|
|
32
|
+
* left inside a line took 5.8 s. Both callers now end a line at every CR, so no
|
|
33
|
+
* line reaches the text holding one; the lookahead stays so a caller that splits
|
|
34
|
+
* otherwise cannot bring the cost back. Only the longest run could ever match,
|
|
35
|
+
* since a shorter one leaves the text the same terminator to cross, so the
|
|
36
|
+
* groups are unchanged.
|
|
20
37
|
*/
|
|
21
|
-
const TASK_PATTERN = /^([ \t]*)[-*+][ \t]+\[([ \txX])\](?:[ \t]+(
|
|
38
|
+
const TASK_PATTERN = /^([ \t]*)(?:[-*+]|\d{1,9}[.)])[ \t]+\[([ \txX])\](?:[ \t]+(?![ \t])([^\r\n]*))?$/;
|
|
22
39
|
export function parseTasksFromContent(content) {
|
|
23
40
|
const tasks = [];
|
|
24
41
|
// normalizeDocument so a leading BOM cannot hide the first line:
|
|
@@ -116,22 +133,28 @@ export async function getTaskProgressForChange(changesDir, changeName, projectRo
|
|
|
116
133
|
*/
|
|
117
134
|
export async function setTaskDone(file, line, done) {
|
|
118
135
|
const content = await fs.readFile(file, 'utf-8');
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
|
|
136
|
+
// Split where `parseTasksFromContent` splits (BOM stripped; CRLF, LF and a
|
|
137
|
+
// bare CR each end a line), so `line` means the line it numbered: a '\n'-only
|
|
138
|
+
// split joined the lines around a bare CR and shifted every later one. A bare
|
|
139
|
+
// CR ends a line rather than staying text because CommonMark ends one there,
|
|
140
|
+
// and a task GitHub draws after it would otherwise go uncounted. The endings
|
|
141
|
+
// are rejoined as they were.
|
|
142
|
+
const text = stripBom(content);
|
|
143
|
+
const lines = text.split(/\r\n?|\n/);
|
|
144
|
+
const endings = text.match(/\r\n?|\n/g) ?? [];
|
|
123
145
|
const raw = lines[line - 1];
|
|
124
146
|
// A fenced line is "not a task" for the same reason it is not counted: it is a
|
|
125
147
|
// quoted example, and writing to it edits documentation. The UI draws no
|
|
126
148
|
// checkbox there, so reaching here means a stale client — hence the caller's 409.
|
|
127
149
|
const fenced = buildCodeFenceMask(lines);
|
|
128
|
-
if (raw === undefined || fenced[line - 1] || !TASK_PATTERN.test(raw
|
|
150
|
+
if (raw === undefined || fenced[line - 1] || !TASK_PATTERN.test(raw)) {
|
|
129
151
|
throw new Error(`Line ${line} of ${path.basename(file)} is not a task`);
|
|
130
152
|
}
|
|
131
153
|
// Same box class as TASK_PATTERN, so this rewrites exactly the marker the line
|
|
132
|
-
// matched on; the first `[…]` on a task line is always it
|
|
154
|
+
// matched on; the first `[…]` on a task line is always it, ordered list
|
|
155
|
+
// included (`1.` / `1)` carry no bracket).
|
|
133
156
|
lines[line - 1] = raw.replace(/\[[ \txX]\]/, done ? '[x]' : '[ ]');
|
|
134
|
-
await fs.writeFile(file, lines.join('
|
|
157
|
+
await fs.writeFile(file, lines.map((l, i) => l + (endings[i] ?? '')).join(''), 'utf-8');
|
|
135
158
|
}
|
|
136
159
|
/**
|
|
137
160
|
* "Tasks done", not "Complete": this counts checkboxes, while `tospec status`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-progress.js","sourceRoot":"","sources":["../../src/utils/task-progress.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACxF,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,qCAAqC,CAAC;AACtG,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAE9D
|
|
1
|
+
{"version":3,"file":"task-progress.js","sourceRoot":"","sources":["../../src/utils/task-progress.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,10 +3,37 @@
|
|
|
3
3
|
* archive dirs and ticket files — agents must never assemble one by hand.
|
|
4
4
|
*/
|
|
5
5
|
export declare function formatTimestamp(date: Date): string;
|
|
6
|
+
/**
|
|
7
|
+
* Splits `<yyyyMMdd_HHmmss>-<name>` (an archive dir, a ticket file's base), or
|
|
8
|
+
* null when the value does not open with a stamp. Splitting on the first hyphen
|
|
9
|
+
* alone read `no-date-change` as stamp `no`, name `date-change`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseStampedName(value: string): {
|
|
12
|
+
stamp: string;
|
|
13
|
+
name: string;
|
|
14
|
+
} | null;
|
|
6
15
|
/**
|
|
7
16
|
* A Date as `yyyy-MM-dd` in local time. For date-only metadata, instead of
|
|
8
17
|
* `toISOString().split('T')[0]`, which truncates in UTC and records the wrong
|
|
9
18
|
* calendar day near local midnight.
|
|
10
19
|
*/
|
|
11
20
|
export declare function formatLocalDate(date?: Date): string;
|
|
21
|
+
/**
|
|
22
|
+
* A Date as `yyyy-MM-ddTHH:mm:ss` in local time, with no zone: the `created`
|
|
23
|
+
* a new change records. The day alone could not order two changes made on one
|
|
24
|
+
* day, and validate needs that order to say which of two changes rewriting one
|
|
25
|
+
* requirement archives first.
|
|
26
|
+
*/
|
|
27
|
+
export declare function formatLocalDateTime(date?: Date): string;
|
|
28
|
+
/** Both spellings of `created`: the datetime a change records now, and the day older changes recorded. */
|
|
29
|
+
export declare const CREATED_PATTERN: RegExp;
|
|
30
|
+
/**
|
|
31
|
+
* A `created` value as the local Date it names, and whether it carried a time;
|
|
32
|
+
* null when it matches neither spelling or names no real moment (`2026-13-45`
|
|
33
|
+
* and `25:00:00` would roll over into another one).
|
|
34
|
+
*/
|
|
35
|
+
export declare function parseCreated(value: string): {
|
|
36
|
+
date: Date;
|
|
37
|
+
hasTime: boolean;
|
|
38
|
+
} | null;
|
|
12
39
|
//# sourceMappingURL=timestamp.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timestamp.d.ts","sourceRoot":"","sources":["../../src/utils/timestamp.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CASlD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE,IAAiB,GAAG,MAAM,CAG/D"}
|
|
1
|
+
{"version":3,"file":"timestamp.d.ts","sourceRoot":"","sources":["../../src/utils/timestamp.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CASlD;AAID;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAGtF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE,IAAiB,GAAG,MAAM,CAG/D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,GAAE,IAAiB,GAAG,MAAM,CAGnE;AAED,0GAA0G;AAC1G,eAAO,MAAM,eAAe,QAA2D,CAAC;AAExF;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAQnF"}
|
package/dist/utils/timestamp.js
CHANGED
|
@@ -12,6 +12,16 @@ export function formatTimestamp(date) {
|
|
|
12
12
|
const seconds = pad(date.getSeconds());
|
|
13
13
|
return `${year}${month}${day}_${hours}${minutes}${seconds}`;
|
|
14
14
|
}
|
|
15
|
+
const STAMPED_NAME = /^(\d{8}_\d{6})-(.+)$/;
|
|
16
|
+
/**
|
|
17
|
+
* Splits `<yyyyMMdd_HHmmss>-<name>` (an archive dir, a ticket file's base), or
|
|
18
|
+
* null when the value does not open with a stamp. Splitting on the first hyphen
|
|
19
|
+
* alone read `no-date-change` as stamp `no`, name `date-change`.
|
|
20
|
+
*/
|
|
21
|
+
export function parseStampedName(value) {
|
|
22
|
+
const match = STAMPED_NAME.exec(value);
|
|
23
|
+
return match ? { stamp: match[1], name: match[2] } : null;
|
|
24
|
+
}
|
|
15
25
|
/**
|
|
16
26
|
* A Date as `yyyy-MM-dd` in local time. For date-only metadata, instead of
|
|
17
27
|
* `toISOString().split('T')[0]`, which truncates in UTC and records the wrong
|
|
@@ -21,4 +31,31 @@ export function formatLocalDate(date = new Date()) {
|
|
|
21
31
|
const pad = (n) => String(n).padStart(2, '0');
|
|
22
32
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
23
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* A Date as `yyyy-MM-ddTHH:mm:ss` in local time, with no zone: the `created`
|
|
36
|
+
* a new change records. The day alone could not order two changes made on one
|
|
37
|
+
* day, and validate needs that order to say which of two changes rewriting one
|
|
38
|
+
* requirement archives first.
|
|
39
|
+
*/
|
|
40
|
+
export function formatLocalDateTime(date = new Date()) {
|
|
41
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
42
|
+
return `${formatLocalDate(date)}T${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
|
43
|
+
}
|
|
44
|
+
/** Both spellings of `created`: the datetime a change records now, and the day older changes recorded. */
|
|
45
|
+
export const CREATED_PATTERN = /^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2}):(\d{2}))?$/;
|
|
46
|
+
/**
|
|
47
|
+
* A `created` value as the local Date it names, and whether it carried a time;
|
|
48
|
+
* null when it matches neither spelling or names no real moment (`2026-13-45`
|
|
49
|
+
* and `25:00:00` would roll over into another one).
|
|
50
|
+
*/
|
|
51
|
+
export function parseCreated(value) {
|
|
52
|
+
const match = CREATED_PATTERN.exec(value);
|
|
53
|
+
if (!match)
|
|
54
|
+
return null;
|
|
55
|
+
const [, y, mo, d, h, mi, s] = match;
|
|
56
|
+
const hasTime = h !== undefined;
|
|
57
|
+
const date = hasTime ? new Date(+y, +mo - 1, +d, +h, +mi, +s) : new Date(+y, +mo - 1, +d);
|
|
58
|
+
const roundTrip = hasTime ? formatLocalDateTime(date) : formatLocalDate(date);
|
|
59
|
+
return roundTrip === value ? { date, hasTime } : null;
|
|
60
|
+
}
|
|
24
61
|
//# sourceMappingURL=timestamp.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timestamp.js","sourceRoot":"","sources":["../../src/utils/timestamp.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,IAAU;IACxC,MAAM,GAAG,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;IACvC,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IAChC,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;IACvC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;IACvC,OAAO,GAAG,IAAI,GAAG,KAAK,GAAG,GAAG,IAAI,KAAK,GAAG,OAAO,GAAG,OAAO,EAAE,CAAC;AAC9D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,IAAI,GAAS,IAAI,IAAI,EAAE;IACrD,MAAM,GAAG,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC9D,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;AACpF,CAAC"}
|
|
1
|
+
{"version":3,"file":"timestamp.js","sourceRoot":"","sources":["../../src/utils/timestamp.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seanmars/tospec",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Spec-driven development CLI for structured requirements and issue workflows",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://www.npmjs.com/package/@seanmars/tospec",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"node": ">=22.22.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
+
"@types/cross-spawn": "^6.0.6",
|
|
37
38
|
"@types/node": "^24.13.4",
|
|
38
39
|
"typescript": "^7.0.2",
|
|
39
40
|
"vitest": "^5.0.0"
|
|
@@ -42,8 +43,10 @@
|
|
|
42
43
|
"@inquirer/core": "^12.0.3",
|
|
43
44
|
"@inquirer/prompts": "^8.7.2",
|
|
44
45
|
"commander": "^15.0.0",
|
|
46
|
+
"cross-spawn": "^7.0.6",
|
|
45
47
|
"diff": "^9.0.0",
|
|
46
48
|
"fast-glob": "^3.3.3",
|
|
49
|
+
"fast-string-width": "^3.0.2",
|
|
47
50
|
"marked": "^18.0.12",
|
|
48
51
|
"yaml": "^2.9.0",
|
|
49
52
|
"zod": "^4.6.2"
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
|
|
25
25
|
## Related Changes
|
|
26
26
|
<!-- Optional, maintained by hand: nothing writes this section. `tospec new change --decisions <file>`
|
|
27
|
-
|
|
28
|
-
once archived; omit if none yet -->
|
|
27
|
+
and `tospec decision new --change <name>` record the link on the change's side only. List change
|
|
28
|
+
names here yourself, add archive paths once archived; omit if none yet -->
|
|
29
29
|
|
|
30
30
|
## Decision Process
|
|
31
31
|
<!-- Optional: the key Q&A that shaped this decision; each pair starts with **Q:**, answer in the next paragraph; summarize, don't paste a transcript; omit if there was no interactive discussion -->
|
|
@@ -12,15 +12,19 @@ artifacts:
|
|
|
12
12
|
requires: []
|
|
13
13
|
instruction: |
|
|
14
14
|
Diagnose and write the issue task: first build a tight feedback loop (one
|
|
15
|
-
command that turns this bug red), list
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
command that turns this bug red), list as many falsifiable hypotheses as
|
|
16
|
+
the evidence supports (one or two is fine when the loop already points at
|
|
17
|
+
one cause) and rank them, verify each, then write down the root cause.
|
|
18
|
+
Redact secrets as <REDACTED> in everything pasted here: task.md is
|
|
19
|
+
committed and kept in the archive. Quote only the lines that carry the
|
|
20
|
+
signal, and do not redact the error message itself. A step that needs
|
|
21
|
+
the user's login is theirs to run; do not capture its output.
|
|
22
|
+
Contents: root-cause analysis, fix plan, test plan (why existing tests missed it / which edge
|
|
18
23
|
cases are missing), and a task checkbox list.
|
|
19
24
|
validation:
|
|
20
25
|
requiredSections: [Feedback Loop, Root Cause, Fix Plan, Test Plan, Tasks]
|
|
21
26
|
- id: specs
|
|
22
|
-
# Exactly one directory level — same constraint as sdd.
|
|
23
|
-
# tospec/decisions/20260730_014309-delta-spec-layout-one-level.md
|
|
27
|
+
# Exactly one directory level — same constraint as sdd.
|
|
24
28
|
generates: specs/*/spec.md
|
|
25
29
|
description: Optional behavior-changing delta specifications
|
|
26
30
|
template: spec.md
|
|
@@ -29,7 +33,9 @@ artifacts:
|
|
|
29
33
|
instruction: |
|
|
30
34
|
Write delta specs only when the fix changes externally observable behavior
|
|
31
35
|
(same grammar as sdd, same layout: specs/<capability>/spec.md, exactly one
|
|
32
|
-
directory level).
|
|
36
|
+
directory level). Use the capability's exact name from tospec/specs/,
|
|
37
|
+
letter case included; a new capability gets a kebab-case name, same rule
|
|
38
|
+
as sdd. REMOVED lists each name as a bullet with Reason and
|
|
33
39
|
Migration fields; RENAMED writes a FROM:/TO: pair, e.g. "- FROM: `<old
|
|
34
40
|
name>`" / "- TO: `<new name>`". If the bug stems from a wrong spec, fix
|
|
35
41
|
that Requirement with MODIFIED — pasting the full updated block, since a
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
## Tasks
|
|
17
17
|
### 1. Fix
|
|
18
|
-
- [ ] 1.1
|
|
18
|
+
- [ ] 1.1 Regression test is red (the one from diagnosis, or write it now)
|
|
19
19
|
- [ ] 1.2 Implement the fix until the test passes (green)
|
|
20
20
|
- [ ] 1.3 Add edge-case tests: [...]
|
|
21
21
|
- [ ] 1.4 Run the full test suite to confirm no regressions
|
package/schemas/sdd/schema.yaml
CHANGED
|
@@ -15,9 +15,14 @@ artifacts:
|
|
|
15
15
|
add/modify/remove), Capabilities (New/Modified capabilities and their specs),
|
|
16
16
|
Impact (affected code/APIs/dependencies/systems). This is what specs and
|
|
17
17
|
design expand from; never write file paths or code snippets (they go stale).
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
.
|
|
18
|
+
Name a capability after the long-lived functional area it covers (`auth`),
|
|
19
|
+
not after this change (`add-login-endpoint`), in kebab-case; a Modified
|
|
20
|
+
capability keeps its exact existing name. When the change falls inside
|
|
21
|
+
an existing capability's area, list that one under Modified Capabilities;
|
|
22
|
+
create a new capability only when the area is genuinely different.
|
|
23
|
+
If the user explicitly says this change needs no delta spec, say so in
|
|
24
|
+
Impact and set `skip_specs: true` in the change's .tospec.yaml — see the
|
|
25
|
+
specs artifact below.
|
|
21
26
|
validation:
|
|
22
27
|
requiredSections: [Why, What Changes, Impact]
|
|
23
28
|
minSectionLength:
|
|
@@ -29,8 +34,7 @@ artifacts:
|
|
|
29
34
|
- id: specs
|
|
30
35
|
# Exactly one directory level. A deeper path (specs/<area>/<capability>/spec.md)
|
|
31
36
|
# is rejected by the validator: archive's merge only reads one level, so a nested
|
|
32
|
-
# file would validate clean and then vanish on archive.
|
|
33
|
-
# tospec/decisions/20260730_014309-delta-spec-layout-one-level.md
|
|
37
|
+
# file would validate clean and then vanish on archive.
|
|
34
38
|
generates: specs/*/spec.md
|
|
35
39
|
description: Delta specifications for affected capabilities
|
|
36
40
|
template: spec.md
|
|
@@ -38,6 +42,13 @@ artifacts:
|
|
|
38
42
|
instruction: |
|
|
39
43
|
Write delta specs from the proposal. One file per capability, at
|
|
40
44
|
specs/<capability>/spec.md — exactly one directory level, never nested.
|
|
45
|
+
An existing capability's folder uses its exact name from tospec/specs/,
|
|
46
|
+
letter case included: a name differing only in case is rejected. A new
|
|
47
|
+
capability's folder name becomes its main spec's name, so write it in
|
|
48
|
+
kebab-case (lowercase letters, numbers, single hyphens; letters with no
|
|
49
|
+
case, such as 中文, are fine); validate warns otherwise. A name Windows
|
|
50
|
+
cannot hold (con, aux, nul, prn, com1-9, lpt1-9, any of <>:"/\|?*, or a
|
|
51
|
+
trailing dot or space) is rejected.
|
|
41
52
|
Grammar: ## ADDED|MODIFIED|REMOVED|RENAMED Requirements /
|
|
42
53
|
### Requirement: <name> (state the obligation with SHALL or MUST in the
|
|
43
54
|
body, not only in the heading) /
|
|
@@ -60,11 +71,11 @@ artifacts:
|
|
|
60
71
|
capability exists); archive carries it into the newly created main spec. Do
|
|
61
72
|
not add Purpose to an existing capability's delta (edit the main spec's
|
|
62
73
|
Purpose directly instead).
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
When the user explicitly says this change needs no delta spec, write no
|
|
75
|
+
delta and declare `skip_specs: true` in its .tospec.yaml instead. Never
|
|
76
|
+
set the marker on your own judgment, never invent a Requirement to get
|
|
77
|
+
past validation, and never set the marker on a change that does have
|
|
78
|
+
delta specs (validation rejects that contradiction).
|
|
68
79
|
- id: design
|
|
69
80
|
generates: design.md
|
|
70
81
|
description: Technical design and testing seams
|