@nusoft/nuos-build-catalogue 0.12.0 → 0.14.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/dist/cli.js +54 -41
- package/dist/commands/init.d.ts +12 -2
- package/dist/commands/init.js +136 -74
- package/dist/commands/plan.d.ts +12 -0
- package/dist/commands/plan.js +83 -0
- package/dist/commands/write.js +16 -5
- package/dist/path-resolution.d.ts +68 -0
- package/dist/path-resolution.js +147 -0
- package/dist/runtime/ac-parse.js +10 -6
- package/dist/runtime/markdown-edit.d.ts +5 -0
- package/dist/runtime/markdown-edit.js +13 -6
- package/dist/runtime/mis-adapter.js +7 -2
- package/package.json +2 -2
- package/templates/hooks/install-hooks.sh +44 -0
- package/templates/hooks/post-commit +96 -0
- package/templates/hooks/pre-commit +162 -0
- package/templates/protocols/end-of-session.md +101 -13
- package/templates/protocols/persona-new.md +64 -30
- package/templates/protocols/plan-orientation.md +122 -0
- package/templates/protocols/start-of-session.md +52 -13
- package/templates/protocols/wu-new.md +75 -50
- package/templates/starter-kit/docs/build/GLOSSARY.md +115 -0
- package/templates/starter-kit/docs/build/STATE.md +30 -16
- package/templates/starter-kit/docs/build/WELCOME.md +79 -0
- package/templates/starter-kit/docs/build/architecture/_index.md +39 -0
- package/templates/starter-kit/docs/build/architecture/module-template.md +47 -0
- package/templates/starter-kit/docs/build/contracts/_index.md +39 -0
- package/templates/starter-kit/docs/build/contracts/contract-template.md +64 -0
- package/templates/starter-kit/docs/build/decisions/_index.md +21 -17
- package/templates/starter-kit/docs/build/design-system/_index.md +57 -0
- package/templates/starter-kit/docs/build/design-system/accessibility.md +77 -0
- package/templates/starter-kit/docs/build/design-system/components/_index.md +29 -0
- package/templates/starter-kit/docs/build/design-system/components/_template.md +60 -0
- package/templates/starter-kit/docs/build/design-system/patterns/_index.md +37 -0
- package/templates/starter-kit/docs/build/design-system/patterns/_template.md +57 -0
- package/templates/starter-kit/docs/build/design-system/tokens-colour.md +52 -0
- package/templates/starter-kit/docs/build/design-system/tokens-motion.md +42 -0
- package/templates/starter-kit/docs/build/design-system/tokens-radius-elevation.md +34 -0
- package/templates/starter-kit/docs/build/design-system/tokens-spacing.md +48 -0
- package/templates/starter-kit/docs/build/design-system/tokens-typography.md +46 -0
- package/templates/starter-kit/docs/build/design-system/voice.md +53 -0
- package/templates/starter-kit/docs/build/maps/01-template.md +15 -112
- package/templates/starter-kit/docs/build/maps/02-template.md +52 -0
- package/templates/starter-kit/docs/build/maps/03-template.md +46 -0
- package/templates/starter-kit/docs/build/maps/99-template-power-user-operational-plan.md +126 -0
- package/templates/starter-kit/docs/build/maps/_index.md +17 -52
- package/templates/starter-kit/docs/build/open-questions/_index.md +27 -13
- package/templates/starter-kit/docs/build/personas/_index.md +26 -60
- package/templates/starter-kit/docs/build/risks/_index.md +20 -13
- package/templates/starter-kit/docs/build/sessions/_index.md +18 -16
- package/templates/starter-kit/docs/build/ui-ux/_index.md +48 -0
- package/templates/starter-kit/docs/build/ui-ux/surface-template.md +72 -0
- package/templates/starter-kit/docs/build/work-units/001-template-simple.md +43 -0
- package/templates/starter-kit/docs/build/work-units/_index.md +18 -20
- package/templates/starter-kit/methodfile.json +19 -8
- /package/templates/starter-kit/docs/build/work-units/{001-template.md → 001-template-full.md} +0 -0
package/dist/commands/write.js
CHANGED
|
@@ -86,16 +86,25 @@ function inferWorkflowStatus(record) {
|
|
|
86
86
|
}
|
|
87
87
|
// ---------------------------------------------------------------------------
|
|
88
88
|
// wu tick <handle> --index=N --evidence="..."
|
|
89
|
+
//
|
|
90
|
+
// --index is **1-based** at the CLI boundary (so --index=1 ticks the first
|
|
91
|
+
// AC). Internally the pack workflow + ac-parse use 0-based indexing; the
|
|
92
|
+
// conversion happens here so the user-facing surface matches the
|
|
93
|
+
// human-readable summary text the mis-adapter writes into the markdown
|
|
94
|
+
// changelog ("Acceptance criterion 3 ticked: ...").
|
|
89
95
|
// ---------------------------------------------------------------------------
|
|
90
96
|
export async function cmdWuTick(store, runtime, args) {
|
|
91
97
|
if (!args.handle) {
|
|
92
98
|
return {
|
|
93
|
-
output: 'Usage: nuos-catalogue wu tick <handle> --index=N --evidence="..."',
|
|
99
|
+
output: 'Usage: nuos-catalogue wu tick <handle> --index=N --evidence="..." (--index is 1-based)',
|
|
94
100
|
exitCode: 2,
|
|
95
101
|
};
|
|
96
102
|
}
|
|
97
|
-
if (typeof args.index !== 'number' || !Number.isInteger(args.index) || args.index <
|
|
98
|
-
return {
|
|
103
|
+
if (typeof args.index !== 'number' || !Number.isInteger(args.index) || args.index < 1) {
|
|
104
|
+
return {
|
|
105
|
+
output: '--index=<positive integer> is required (1-based: --index=1 ticks the first AC)',
|
|
106
|
+
exitCode: 2,
|
|
107
|
+
};
|
|
99
108
|
}
|
|
100
109
|
if (!args.evidence || args.evidence.trim().length === 0) {
|
|
101
110
|
return { output: '--evidence="..." is required (non-empty)', exitCode: 2 };
|
|
@@ -104,17 +113,19 @@ export async function cmdWuTick(store, runtime, args) {
|
|
|
104
113
|
if (!store.has(handle)) {
|
|
105
114
|
return { output: `no work_unit record for handle "${handle}"`, exitCode: 1 };
|
|
106
115
|
}
|
|
116
|
+
// Convert 1-based CLI input to 0-based for the workflow + ac-parse layer.
|
|
117
|
+
const zeroBasedIndex = args.index - 1;
|
|
107
118
|
const capture = {
|
|
108
119
|
channel: 'typed_note',
|
|
109
120
|
content: `tick AC #${args.index} on ${handle}`,
|
|
110
121
|
subjects: [{ kind: 'work_unit', id: handle }],
|
|
111
122
|
metadata: {
|
|
112
123
|
targetHandle: handle,
|
|
113
|
-
criterionIndex:
|
|
124
|
+
criterionIndex: zeroBasedIndex,
|
|
114
125
|
evidence: args.evidence,
|
|
115
126
|
},
|
|
116
127
|
};
|
|
117
|
-
return await driveLifecycle(runtime, 'work_unit.tick_acceptance_criterion', capture, handle, `
|
|
128
|
+
return await driveLifecycle(runtime, 'work_unit.tick_acceptance_criterion', capture, handle, `AC ${args.index}`);
|
|
118
129
|
}
|
|
119
130
|
// ---------------------------------------------------------------------------
|
|
120
131
|
// decision supersede <target> --by=<superseding> [--reason="..."]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default path resolution for the CLI.
|
|
3
|
+
*
|
|
4
|
+
* Defaults walk up from `process.cwd()` to find the nearest directory
|
|
5
|
+
* containing `docs/build/`, the same way `git` finds its repo root.
|
|
6
|
+
* That makes the CLI work consistently regardless of where it was
|
|
7
|
+
* installed from (sibling checkout, npx cache, global install) and
|
|
8
|
+
* regardless of which subdirectory the operator invokes it from.
|
|
9
|
+
*
|
|
10
|
+
* Resolution order, for every path-shaped flag:
|
|
11
|
+
* 1. Explicit `--flag=<value>` (highest precedence)
|
|
12
|
+
* 2. Matching `NUOS_CATALOGUE_*` env var
|
|
13
|
+
* 3. Walk-up discovery from cwd
|
|
14
|
+
* 4. Throw with a clear hint, OR fall back to cwd, depending on the
|
|
15
|
+
* flag's semantics (build-root throws because there's no honest
|
|
16
|
+
* default; storage dirs fall back to cwd because creating them
|
|
17
|
+
* ad-hoc is reasonable when no project root is found).
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Walk up from `startDir` looking for a directory that contains a
|
|
21
|
+
* `docs/build/` subdirectory. Returns the absolute path to the
|
|
22
|
+
* containing directory, or null if no such directory is found before
|
|
23
|
+
* reaching the filesystem root.
|
|
24
|
+
*
|
|
25
|
+
* Exposed for testing; ordinary callers use `resolveBuildRoot` etc.
|
|
26
|
+
*/
|
|
27
|
+
export declare function findProjectRoot(startDir: string): string | null;
|
|
28
|
+
export interface ResolutionContext {
|
|
29
|
+
/** Override for `process.cwd()` — used by tests to anchor walk-up. */
|
|
30
|
+
cwd?: string;
|
|
31
|
+
/** Override for `process.env` — used by tests to control env vars. */
|
|
32
|
+
env?: NodeJS.ProcessEnv;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Resolve the build catalogue root. Throws with a clear hint when no
|
|
36
|
+
* value is available — the build root is load-bearing for every write
|
|
37
|
+
* command, so a silent fallback would mask real errors.
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveBuildRoot(flag: string | boolean | undefined, ctx?: ResolutionContext): string;
|
|
40
|
+
/**
|
|
41
|
+
* Resolve the wider documentation root for semantic-search indexing.
|
|
42
|
+
* Falls back to `<project-root>/docs` when no flag or env var is set,
|
|
43
|
+
* because semantic-search has always indexed the wider docs/ surface,
|
|
44
|
+
* not just docs/build/.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveCatalogueRoot(flag: string | boolean | undefined, ctx?: ResolutionContext): string;
|
|
47
|
+
/**
|
|
48
|
+
* Resolve the `.nuos-catalogue/` storage directory. Always co-located
|
|
49
|
+
* with the project root (the directory containing `docs/build/`).
|
|
50
|
+
* `NUOS_CATALOGUE_INDEX_DIR` env var wins when set.
|
|
51
|
+
*/
|
|
52
|
+
export declare function resolveIndexDir(buildRoot: string, ctx?: ResolutionContext): string;
|
|
53
|
+
export declare function resolveWorkflowsPath(buildRoot: string, flag: string | boolean | undefined, ctx?: ResolutionContext): string;
|
|
54
|
+
export declare function resolveIndexPath(buildRoot: string, flag: string | boolean | undefined, ctx?: ResolutionContext): string;
|
|
55
|
+
export declare function resolveHashPath(buildRoot: string, flag: string | boolean | undefined, ctx?: ResolutionContext): string;
|
|
56
|
+
/**
|
|
57
|
+
* Soft warning surfaced after a `migrate` or `regenerate` run: if the
|
|
58
|
+
* project has a `.gitignore` at its root and that `.gitignore` does
|
|
59
|
+
* NOT contain a `.nuos-catalogue/` entry, the workflow store appears
|
|
60
|
+
* as untracked. Returns a multi-line `note:` string when a warning
|
|
61
|
+
* should be printed, or null when silent.
|
|
62
|
+
*
|
|
63
|
+
* Silent when:
|
|
64
|
+
* - the project has no `.gitignore` (it might not be a git repo)
|
|
65
|
+
* - the gitignore already excludes `.nuos-catalogue/`
|
|
66
|
+
* - the gitignore can't be read for any reason (be quiet, not noisy)
|
|
67
|
+
*/
|
|
68
|
+
export declare function gitignoreCatalogueNote(buildRoot: string, ctx?: ResolutionContext): string | null;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default path resolution for the CLI.
|
|
3
|
+
*
|
|
4
|
+
* Defaults walk up from `process.cwd()` to find the nearest directory
|
|
5
|
+
* containing `docs/build/`, the same way `git` finds its repo root.
|
|
6
|
+
* That makes the CLI work consistently regardless of where it was
|
|
7
|
+
* installed from (sibling checkout, npx cache, global install) and
|
|
8
|
+
* regardless of which subdirectory the operator invokes it from.
|
|
9
|
+
*
|
|
10
|
+
* Resolution order, for every path-shaped flag:
|
|
11
|
+
* 1. Explicit `--flag=<value>` (highest precedence)
|
|
12
|
+
* 2. Matching `NUOS_CATALOGUE_*` env var
|
|
13
|
+
* 3. Walk-up discovery from cwd
|
|
14
|
+
* 4. Throw with a clear hint, OR fall back to cwd, depending on the
|
|
15
|
+
* flag's semantics (build-root throws because there's no honest
|
|
16
|
+
* default; storage dirs fall back to cwd because creating them
|
|
17
|
+
* ad-hoc is reasonable when no project root is found).
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
/**
|
|
22
|
+
* Walk up from `startDir` looking for a directory that contains a
|
|
23
|
+
* `docs/build/` subdirectory. Returns the absolute path to the
|
|
24
|
+
* containing directory, or null if no such directory is found before
|
|
25
|
+
* reaching the filesystem root.
|
|
26
|
+
*
|
|
27
|
+
* Exposed for testing; ordinary callers use `resolveBuildRoot` etc.
|
|
28
|
+
*/
|
|
29
|
+
export function findProjectRoot(startDir) {
|
|
30
|
+
let dir = path.resolve(startDir);
|
|
31
|
+
while (true) {
|
|
32
|
+
if (existsSync(path.join(dir, 'docs', 'build')))
|
|
33
|
+
return dir;
|
|
34
|
+
const parent = path.dirname(dir);
|
|
35
|
+
if (parent === dir)
|
|
36
|
+
return null;
|
|
37
|
+
dir = parent;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function ctxCwd(ctx) {
|
|
41
|
+
return ctx?.cwd ?? process.cwd();
|
|
42
|
+
}
|
|
43
|
+
function ctxEnv(ctx) {
|
|
44
|
+
return ctx?.env ?? process.env;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolve the build catalogue root. Throws with a clear hint when no
|
|
48
|
+
* value is available — the build root is load-bearing for every write
|
|
49
|
+
* command, so a silent fallback would mask real errors.
|
|
50
|
+
*/
|
|
51
|
+
export function resolveBuildRoot(flag, ctx) {
|
|
52
|
+
if (typeof flag === 'string' && flag.length > 0)
|
|
53
|
+
return path.resolve(flag);
|
|
54
|
+
const env = ctxEnv(ctx);
|
|
55
|
+
if (env.NUOS_CATALOGUE_BUILD_ROOT)
|
|
56
|
+
return path.resolve(env.NUOS_CATALOGUE_BUILD_ROOT);
|
|
57
|
+
const root = findProjectRoot(ctxCwd(ctx));
|
|
58
|
+
if (root)
|
|
59
|
+
return path.join(root, 'docs', 'build');
|
|
60
|
+
throw new Error('cannot locate the build catalogue (no docs/build/ directory found from cwd or any parent).\n' +
|
|
61
|
+
'Either:\n' +
|
|
62
|
+
' - run from a project that has been bootstrapped via `nuos-catalogue init`,\n' +
|
|
63
|
+
' - set NUOS_CATALOGUE_BUILD_ROOT in your shell profile,\n' +
|
|
64
|
+
' - or pass --build-root=<dir> to this command.');
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Resolve the wider documentation root for semantic-search indexing.
|
|
68
|
+
* Falls back to `<project-root>/docs` when no flag or env var is set,
|
|
69
|
+
* because semantic-search has always indexed the wider docs/ surface,
|
|
70
|
+
* not just docs/build/.
|
|
71
|
+
*/
|
|
72
|
+
export function resolveCatalogueRoot(flag, ctx) {
|
|
73
|
+
if (typeof flag === 'string' && flag.length > 0)
|
|
74
|
+
return path.resolve(flag);
|
|
75
|
+
const env = ctxEnv(ctx);
|
|
76
|
+
if (env.NUOS_CATALOGUE_ROOT)
|
|
77
|
+
return path.resolve(env.NUOS_CATALOGUE_ROOT);
|
|
78
|
+
const root = findProjectRoot(ctxCwd(ctx));
|
|
79
|
+
if (root)
|
|
80
|
+
return path.join(root, 'docs');
|
|
81
|
+
throw new Error('cannot locate the docs/ directory (no docs/build/ found from cwd or any parent).\n' +
|
|
82
|
+
'Either set NUOS_CATALOGUE_ROOT, or pass --catalogue=<dir>.');
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Resolve the `.nuos-catalogue/` storage directory. Always co-located
|
|
86
|
+
* with the project root (the directory containing `docs/build/`).
|
|
87
|
+
* `NUOS_CATALOGUE_INDEX_DIR` env var wins when set.
|
|
88
|
+
*/
|
|
89
|
+
export function resolveIndexDir(buildRoot, ctx) {
|
|
90
|
+
const env = ctxEnv(ctx);
|
|
91
|
+
if (env.NUOS_CATALOGUE_INDEX_DIR)
|
|
92
|
+
return path.resolve(env.NUOS_CATALOGUE_INDEX_DIR);
|
|
93
|
+
// buildRoot is `<project-root>/docs/build`; two dirname() calls climb
|
|
94
|
+
// back to the project root.
|
|
95
|
+
const projectRoot = path.dirname(path.dirname(buildRoot));
|
|
96
|
+
return path.join(projectRoot, '.nuos-catalogue');
|
|
97
|
+
}
|
|
98
|
+
export function resolveWorkflowsPath(buildRoot, flag, ctx) {
|
|
99
|
+
if (typeof flag === 'string' && flag.length > 0)
|
|
100
|
+
return path.resolve(flag);
|
|
101
|
+
const env = ctxEnv(ctx);
|
|
102
|
+
if (env.NUOS_CATALOGUE_WORKFLOWS)
|
|
103
|
+
return path.resolve(env.NUOS_CATALOGUE_WORKFLOWS);
|
|
104
|
+
return path.join(resolveIndexDir(buildRoot, ctx), 'workflows.json');
|
|
105
|
+
}
|
|
106
|
+
export function resolveIndexPath(buildRoot, flag, ctx) {
|
|
107
|
+
if (typeof flag === 'string' && flag.length > 0)
|
|
108
|
+
return path.resolve(flag);
|
|
109
|
+
return path.join(resolveIndexDir(buildRoot, ctx), 'index.nv');
|
|
110
|
+
}
|
|
111
|
+
export function resolveHashPath(buildRoot, flag, ctx) {
|
|
112
|
+
if (typeof flag === 'string' && flag.length > 0)
|
|
113
|
+
return path.resolve(flag);
|
|
114
|
+
return path.join(resolveIndexDir(buildRoot, ctx), 'hashes.json');
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Soft warning surfaced after a `migrate` or `regenerate` run: if the
|
|
118
|
+
* project has a `.gitignore` at its root and that `.gitignore` does
|
|
119
|
+
* NOT contain a `.nuos-catalogue/` entry, the workflow store appears
|
|
120
|
+
* as untracked. Returns a multi-line `note:` string when a warning
|
|
121
|
+
* should be printed, or null when silent.
|
|
122
|
+
*
|
|
123
|
+
* Silent when:
|
|
124
|
+
* - the project has no `.gitignore` (it might not be a git repo)
|
|
125
|
+
* - the gitignore already excludes `.nuos-catalogue/`
|
|
126
|
+
* - the gitignore can't be read for any reason (be quiet, not noisy)
|
|
127
|
+
*/
|
|
128
|
+
export function gitignoreCatalogueNote(buildRoot, ctx) {
|
|
129
|
+
try {
|
|
130
|
+
const projectRoot = path.dirname(path.dirname(buildRoot));
|
|
131
|
+
const gitignorePath = path.join(projectRoot, '.gitignore');
|
|
132
|
+
if (!existsSync(gitignorePath))
|
|
133
|
+
return null;
|
|
134
|
+
const content = readFileSync(gitignorePath, 'utf8');
|
|
135
|
+
if (/^\s*\.nuos-catalogue\//m.test(content))
|
|
136
|
+
return null;
|
|
137
|
+
const indexDir = resolveIndexDir(buildRoot, ctx);
|
|
138
|
+
return ('note: the workflow store is at ' +
|
|
139
|
+
path.relative(projectRoot, indexDir) +
|
|
140
|
+
'/, but your .gitignore does not exclude .nuos-catalogue/.\n' +
|
|
141
|
+
' Add this line to .gitignore so the per-machine JSON state stays out of commits:\n' +
|
|
142
|
+
' .nuos-catalogue/');
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
package/dist/runtime/ac-parse.js
CHANGED
|
@@ -160,15 +160,19 @@ export function extractForCompletion(rawMarkdown) {
|
|
|
160
160
|
*/
|
|
161
161
|
function parseHistoryEvidence(rawMarkdown) {
|
|
162
162
|
const result = new Map();
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
// Anchor the heading lookup to start-of-line so prose mentions inside
|
|
164
|
+
// code spans or paragraphs (e.g. a WU's notes log discussing the
|
|
165
|
+
// section by name) don't false-match the literal string.
|
|
166
|
+
const HEADING_RE = /^## Build catalogue history\s*$/m;
|
|
167
|
+
const headingMatch = HEADING_RE.exec(rawMarkdown);
|
|
168
|
+
if (!headingMatch)
|
|
165
169
|
return result;
|
|
170
|
+
const historyHeadingIndex = headingMatch.index;
|
|
171
|
+
const headingLength = headingMatch[0].length;
|
|
166
172
|
const sectionTail = rawMarkdown.slice(historyHeadingIndex);
|
|
167
173
|
// Bound the history section at the next ## heading (if any).
|
|
168
|
-
const nextSectionMatch = /\n## \S/.exec(sectionTail.slice(
|
|
169
|
-
const sectionEnd = nextSectionMatch
|
|
170
|
-
? '## Build catalogue history'.length + nextSectionMatch.index
|
|
171
|
-
: sectionTail.length;
|
|
174
|
+
const nextSectionMatch = /\n## \S/.exec(sectionTail.slice(headingLength));
|
|
175
|
+
const sectionEnd = nextSectionMatch ? headingLength + nextSectionMatch.index : sectionTail.length;
|
|
172
176
|
const section = sectionTail.slice(0, sectionEnd);
|
|
173
177
|
// Split into entries on the top-level `- **<timestamp>**` bullets.
|
|
174
178
|
// Each entry runs until the next top-level bullet or end-of-section.
|
|
@@ -49,5 +49,10 @@ export interface ChangeLogEntry {
|
|
|
49
49
|
* Idempotence: each call appends; running the same workflow twice
|
|
50
50
|
* appends twice. The audit chain in the workflow store is the
|
|
51
51
|
* deduplicating source of truth.
|
|
52
|
+
*
|
|
53
|
+
* Heading detection is anchored to start-of-line via a regex so that
|
|
54
|
+
* prose mentions of the heading text inside code spans or paragraphs
|
|
55
|
+
* (e.g. a WU's notes log discussing the section by name) don't
|
|
56
|
+
* false-match and cause the entry to land in the wrong place.
|
|
52
57
|
*/
|
|
53
58
|
export declare function appendChangeLog(rawMarkdown: string, entry: ChangeLogEntry): string;
|
|
@@ -64,10 +64,18 @@ export function insertStatusLine(rawMarkdown, newStatus) {
|
|
|
64
64
|
* Idempotence: each call appends; running the same workflow twice
|
|
65
65
|
* appends twice. The audit chain in the workflow store is the
|
|
66
66
|
* deduplicating source of truth.
|
|
67
|
+
*
|
|
68
|
+
* Heading detection is anchored to start-of-line via a regex so that
|
|
69
|
+
* prose mentions of the heading text inside code spans or paragraphs
|
|
70
|
+
* (e.g. a WU's notes log discussing the section by name) don't
|
|
71
|
+
* false-match and cause the entry to land in the wrong place.
|
|
67
72
|
*/
|
|
68
73
|
export function appendChangeLog(rawMarkdown, entry) {
|
|
69
74
|
const heading = '## Build catalogue history';
|
|
70
|
-
const
|
|
75
|
+
const HEADING_RE = /^## Build catalogue history\s*$/m;
|
|
76
|
+
const headingMatch = HEADING_RE.exec(rawMarkdown);
|
|
77
|
+
const headingIndex = headingMatch ? headingMatch.index : -1;
|
|
78
|
+
const headingLength = headingMatch ? headingMatch[0].length : 0;
|
|
71
79
|
const detailLines = [];
|
|
72
80
|
if (entry.details)
|
|
73
81
|
detailLines.push(` - ${entry.details}`);
|
|
@@ -82,17 +90,16 @@ export function appendChangeLog(rawMarkdown, entry) {
|
|
|
82
90
|
return `${rawMarkdown}${sep}\n${heading}\n\n${block}\n`;
|
|
83
91
|
}
|
|
84
92
|
// Find the end of the file (or the next section) and insert before that.
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
// next H1/H2 boundary (or EOF) as the section.
|
|
93
|
+
// We treat everything from `headingIndex` to the next H1/H2 boundary
|
|
94
|
+
// (or EOF) as the section's body.
|
|
88
95
|
const tail = rawMarkdown.slice(headingIndex);
|
|
89
|
-
const nextHeadingMatch = /\n##? \S/.exec(tail.slice(
|
|
96
|
+
const nextHeadingMatch = /\n##? \S/.exec(tail.slice(headingLength));
|
|
90
97
|
if (!nextHeadingMatch) {
|
|
91
98
|
// History is the last section — append at end of file.
|
|
92
99
|
const sep = rawMarkdown.endsWith('\n') ? '' : '\n';
|
|
93
100
|
return `${rawMarkdown}${sep}${block}\n`;
|
|
94
101
|
}
|
|
95
|
-
const splitAt = headingIndex +
|
|
102
|
+
const splitAt = headingIndex + headingLength + nextHeadingMatch.index;
|
|
96
103
|
const before = rawMarkdown.slice(0, splitAt);
|
|
97
104
|
const after = rawMarkdown.slice(splitAt);
|
|
98
105
|
const beforeHasTrailingNewline = before.endsWith('\n');
|
|
@@ -245,9 +245,14 @@ async function commitTickAC(store, catalogueRoot, intent) {
|
|
|
245
245
|
throw err;
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
|
+
// criterionIndex is 0-based internally; the audit log uses 1-based
|
|
249
|
+
// numbering consistently across both the structural-tick path and the
|
|
250
|
+
// audit-log-only fallback, so users never see a mix of 0-based and
|
|
251
|
+
// 1-based references in their catalogue history.
|
|
252
|
+
const oneBased = payload.criterionIndex + 1;
|
|
248
253
|
const summary = acText
|
|
249
|
-
? `Acceptance criterion ${
|
|
250
|
-
: `Acceptance criterion
|
|
254
|
+
? `Acceptance criterion ${oneBased} ticked: "${acText}".`
|
|
255
|
+
: `Acceptance criterion ${oneBased} ticked.`;
|
|
251
256
|
const updatedMarkdown = appendChangeLog(workingMarkdown, {
|
|
252
257
|
isoTimestamp: new Date().toISOString(),
|
|
253
258
|
summary: structuralTick ? summary : `${summary} (audit-log-only — AC list not recognised)`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nusoft/nuos-build-catalogue",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "NuOS build-catalogue tooling: semantic search (WU 110) + migration runner that lifts markdown artefacts into JSON-backed workflow records (WU 111, Phase G).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"build": "rm -rf dist && tsc && chmod +x dist/cli.js",
|
|
20
20
|
"prepublishOnly": "npm run build",
|
|
21
21
|
"verify-storage": "tsx scripts/verify-persistence.ts",
|
|
22
|
-
"test": "tsx --test tests/chunk.test.ts tests/metadata.test.ts tests/crawl.test.ts tests/migrate.test.ts tests/commands-read.test.ts tests/regenerate.test.ts tests/commands-write.test.ts tests/ac-parse.test.ts tests/create.test.ts tests/init.test.ts",
|
|
22
|
+
"test": "tsx --test tests/chunk.test.ts tests/metadata.test.ts tests/crawl.test.ts tests/migrate.test.ts tests/commands-read.test.ts tests/regenerate.test.ts tests/commands-write.test.ts tests/ac-parse.test.ts tests/create.test.ts tests/init.test.ts tests/wu-111-soak-findings.test.ts tests/plan.test.ts",
|
|
23
23
|
"typecheck": "tsc --noEmit",
|
|
24
24
|
"index": "tsx src/cli.ts index",
|
|
25
25
|
"search": "tsx src/cli.ts search"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Install the catalogue's git hooks into .git/hooks/.
|
|
4
|
+
#
|
|
5
|
+
# Why this script (not husky):
|
|
6
|
+
# The nuos repo is a markdown catalogue, not an npm package. Adding a
|
|
7
|
+
# package.json + node_modules just to install hooks would be infrastructure
|
|
8
|
+
# tax for what is otherwise a doc repo. A small bash installer copies the
|
|
9
|
+
# hooks from the version-controlled scripts/hooks/ into .git/hooks/.
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# bash scripts/install-hooks.sh
|
|
13
|
+
#
|
|
14
|
+
# Re-run any time scripts/hooks/ changes; the installer is idempotent.
|
|
15
|
+
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
REPO_ROOT="$(git rev-parse --show-toplevel)"
|
|
19
|
+
SOURCE="$REPO_ROOT/scripts/hooks"
|
|
20
|
+
TARGET="$REPO_ROOT/.git/hooks"
|
|
21
|
+
|
|
22
|
+
if [[ ! -d "$SOURCE" ]]; then
|
|
23
|
+
echo "✖ scripts/hooks/ not found; nothing to install" >&2
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
mkdir -p "$TARGET"
|
|
28
|
+
|
|
29
|
+
installed=0
|
|
30
|
+
for hook in "$SOURCE"/*; do
|
|
31
|
+
name="$(basename "$hook")"
|
|
32
|
+
cp "$hook" "$TARGET/$name"
|
|
33
|
+
chmod +x "$TARGET/$name"
|
|
34
|
+
installed=$((installed + 1))
|
|
35
|
+
done
|
|
36
|
+
|
|
37
|
+
echo "✓ installed $installed hook(s) into .git/hooks/"
|
|
38
|
+
echo
|
|
39
|
+
echo "Active rules (WU 111 enforcement):"
|
|
40
|
+
echo " • index-drift detection (work-units, decisions, open-questions, risks)"
|
|
41
|
+
echo " • active-decision modification BLOCK (was warning under WU 128 light-touch)"
|
|
42
|
+
echo
|
|
43
|
+
echo "To verify the install: \`git hook list\` (git ≥2.36) or \`ls .git/hooks/\`"
|
|
44
|
+
echo "To uninstall: \`rm .git/hooks/pre-commit\`"
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# NuOS catalogue post-commit hook — auto-refresh the semantic-search index
|
|
4
|
+
# after every commit that touched docs/build/**.
|
|
5
|
+
#
|
|
6
|
+
# Why this hook:
|
|
7
|
+
# `nuos-catalogue search` only finds what's in the NuVector index. The
|
|
8
|
+
# index is hash-based and incremental, but it doesn't update itself —
|
|
9
|
+
# you have to run `nuos-catalogue index` after meaningful changes for
|
|
10
|
+
# new content to be searchable. Running it manually is too easy to
|
|
11
|
+
# forget; the discipline argument that gave us the pre-commit hook
|
|
12
|
+
# (index-drift, accepted-decision immutability) applies equally here.
|
|
13
|
+
#
|
|
14
|
+
# Behaviour:
|
|
15
|
+
# - Skip if the just-landed commit did NOT touch docs/build/** (most
|
|
16
|
+
# code commits don't need a reindex)
|
|
17
|
+
# - Skip if `nuos-catalogue` isn't resolvable (the CLI may not be
|
|
18
|
+
# installed yet on a fresh clone; this hook should never block)
|
|
19
|
+
# - Otherwise: run the index in the BACKGROUND so the user's terminal
|
|
20
|
+
# isn't held while we embed. All output goes to .nuos-enforcement.log.
|
|
21
|
+
#
|
|
22
|
+
# The hook respects two env vars:
|
|
23
|
+
# NUOS_CATALOGUE_INDEX_DIR default: <repo>/.nuos-catalogue
|
|
24
|
+
# NUOS_CATALOGUE_OLLAMA_MODEL passed through to the CLI
|
|
25
|
+
#
|
|
26
|
+
# This hook never blocks. If indexing fails (Ollama not running, model
|
|
27
|
+
# not pulled, dimension mismatch with existing index), the error goes
|
|
28
|
+
# to the log and the user can investigate. The commit itself already
|
|
29
|
+
# landed by the time post-commit fires.
|
|
30
|
+
|
|
31
|
+
set -uo pipefail
|
|
32
|
+
|
|
33
|
+
REPO_ROOT="$(git rev-parse --show-toplevel)"
|
|
34
|
+
LOG="$REPO_ROOT/.nuos-enforcement.log"
|
|
35
|
+
|
|
36
|
+
dim() { printf '\033[2m%s\033[0m\n' "$*"; }
|
|
37
|
+
yellow() { printf '\033[33m%s\033[0m\n' "$*"; }
|
|
38
|
+
|
|
39
|
+
# ---- Skip-paths --------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
# Skip if this project doesn't have a catalogue at all.
|
|
42
|
+
if [[ ! -d "$REPO_ROOT/docs/build" ]]; then
|
|
43
|
+
exit 0
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Skip if the just-landed commit didn't touch docs/build/**.
|
|
47
|
+
if ! git diff-tree --no-commit-id --name-only -r HEAD 2>/dev/null | grep -q '^docs/build/'; then
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# Resolve the CLI: prefer globally-installed `nuos-catalogue`; fall back
|
|
52
|
+
# to `npx --yes` which fetches from npm on demand.
|
|
53
|
+
INDEX_CMD=""
|
|
54
|
+
if command -v nuos-catalogue >/dev/null 2>&1; then
|
|
55
|
+
INDEX_CMD="nuos-catalogue"
|
|
56
|
+
elif command -v npx >/dev/null 2>&1; then
|
|
57
|
+
INDEX_CMD="npx --yes @nusoft/nuos-build-catalogue"
|
|
58
|
+
else
|
|
59
|
+
yellow "[nuos:post-commit] neither nuos-catalogue nor npx found; skipping index refresh"
|
|
60
|
+
printf '%s | post-commit-skip | no CLI available\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" >> "$LOG"
|
|
61
|
+
exit 0
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# ---- Background index refresh ------------------------------------------
|
|
65
|
+
|
|
66
|
+
# Default the index dir to project-local if the user hasn't set one.
|
|
67
|
+
: "${NUOS_CATALOGUE_INDEX_DIR:=$REPO_ROOT/.nuos-catalogue}"
|
|
68
|
+
export NUOS_CATALOGUE_INDEX_DIR
|
|
69
|
+
|
|
70
|
+
# Detach from the terminal so the post-commit returns immediately. All
|
|
71
|
+
# output (stdout + stderr) goes to the enforcement log so the user can
|
|
72
|
+
# tail it if they want to see progress.
|
|
73
|
+
(
|
|
74
|
+
start=$(date +%s)
|
|
75
|
+
printf '%s | post-commit-index | start (model=%s, dir=%s)\n' \
|
|
76
|
+
"$(date -u '+%Y-%m-%dT%H:%M:%SZ')" \
|
|
77
|
+
"${NUOS_CATALOGUE_OLLAMA_MODEL:-default}" \
|
|
78
|
+
"$NUOS_CATALOGUE_INDEX_DIR" \
|
|
79
|
+
>> "$LOG"
|
|
80
|
+
|
|
81
|
+
if cd "$REPO_ROOT" && $INDEX_CMD index >> "$LOG" 2>&1; then
|
|
82
|
+
end=$(date +%s)
|
|
83
|
+
printf '%s | post-commit-index | done in %ss\n' \
|
|
84
|
+
"$(date -u '+%Y-%m-%dT%H:%M:%SZ')" \
|
|
85
|
+
$((end - start)) \
|
|
86
|
+
>> "$LOG"
|
|
87
|
+
else
|
|
88
|
+
printf '%s | post-commit-index | FAILED (see lines above in %s)\n' \
|
|
89
|
+
"$(date -u '+%Y-%m-%dT%H:%M:%SZ')" \
|
|
90
|
+
"$(basename "$LOG")" \
|
|
91
|
+
>> "$LOG"
|
|
92
|
+
fi
|
|
93
|
+
) </dev/null >/dev/null 2>&1 &
|
|
94
|
+
disown 2>/dev/null || true
|
|
95
|
+
|
|
96
|
+
dim "[nuos:post-commit] index refresh started in background — see .nuos-enforcement.log"
|