@ai-substrate/engineering-harness 0.2.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/LICENSE +21 -0
- package/README.md +141 -0
- package/harness/cli/bin/harness.js +12 -0
- package/harness/cli/dist/acts/docs.d.ts +17 -0
- package/harness/cli/dist/acts/docs.js +73 -0
- package/harness/cli/dist/acts/docs.js.map +1 -0
- package/harness/cli/dist/acts/doctor.d.ts +14 -0
- package/harness/cli/dist/acts/doctor.js +43 -0
- package/harness/cli/dist/acts/doctor.js.map +1 -0
- package/harness/cli/dist/acts/help.d.ts +14 -0
- package/harness/cli/dist/acts/help.js +29 -0
- package/harness/cli/dist/acts/help.js.map +1 -0
- package/harness/cli/dist/acts/init.d.ts +22 -0
- package/harness/cli/dist/acts/init.js +61 -0
- package/harness/cli/dist/acts/init.js.map +1 -0
- package/harness/cli/dist/acts/instructions.d.ts +21 -0
- package/harness/cli/dist/acts/instructions.js +75 -0
- package/harness/cli/dist/acts/instructions.js.map +1 -0
- package/harness/cli/dist/acts/new.d.ts +19 -0
- package/harness/cli/dist/acts/new.js +66 -0
- package/harness/cli/dist/acts/new.js.map +1 -0
- package/harness/cli/dist/acts/observe.d.ts +23 -0
- package/harness/cli/dist/acts/observe.js +129 -0
- package/harness/cli/dist/acts/observe.js.map +1 -0
- package/harness/cli/dist/acts/record.d.ts +24 -0
- package/harness/cli/dist/acts/record.js +93 -0
- package/harness/cli/dist/acts/record.js.map +1 -0
- package/harness/cli/dist/acts/skills.d.ts +32 -0
- package/harness/cli/dist/acts/skills.js +256 -0
- package/harness/cli/dist/acts/skills.js.map +1 -0
- package/harness/cli/dist/acts/update.d.ts +23 -0
- package/harness/cli/dist/acts/update.js +297 -0
- package/harness/cli/dist/acts/update.js.map +1 -0
- package/harness/cli/dist/acts/verb.d.ts +27 -0
- package/harness/cli/dist/acts/verb.js +56 -0
- package/harness/cli/dist/acts/verb.js.map +1 -0
- package/harness/cli/dist/adapters/clock/clock-port.d.ts +10 -0
- package/harness/cli/dist/adapters/clock/clock-port.js +2 -0
- package/harness/cli/dist/adapters/clock/clock-port.js.map +1 -0
- package/harness/cli/dist/adapters/clock/fake-clock.d.ts +15 -0
- package/harness/cli/dist/adapters/clock/fake-clock.js +25 -0
- package/harness/cli/dist/adapters/clock/fake-clock.js.map +1 -0
- package/harness/cli/dist/adapters/clock/system-clock.d.ts +5 -0
- package/harness/cli/dist/adapters/clock/system-clock.js +7 -0
- package/harness/cli/dist/adapters/clock/system-clock.js.map +1 -0
- package/harness/cli/dist/adapters/env/env-port.d.ts +17 -0
- package/harness/cli/dist/adapters/env/env-port.js +2 -0
- package/harness/cli/dist/adapters/env/env-port.js.map +1 -0
- package/harness/cli/dist/adapters/env/fake-env.d.ts +15 -0
- package/harness/cli/dist/adapters/env/fake-env.js +24 -0
- package/harness/cli/dist/adapters/env/fake-env.js.map +1 -0
- package/harness/cli/dist/adapters/env/node-env.d.ts +6 -0
- package/harness/cli/dist/adapters/env/node-env.js +16 -0
- package/harness/cli/dist/adapters/env/node-env.js.map +1 -0
- package/harness/cli/dist/adapters/exec/exec-port.d.ts +22 -0
- package/harness/cli/dist/adapters/exec/exec-port.js +2 -0
- package/harness/cli/dist/adapters/exec/exec-port.js.map +1 -0
- package/harness/cli/dist/adapters/exec/fake-exec.d.ts +25 -0
- package/harness/cli/dist/adapters/exec/fake-exec.js +25 -0
- package/harness/cli/dist/adapters/exec/fake-exec.js.map +1 -0
- package/harness/cli/dist/adapters/exec/node-exec.d.ts +14 -0
- package/harness/cli/dist/adapters/exec/node-exec.js +38 -0
- package/harness/cli/dist/adapters/exec/node-exec.js.map +1 -0
- package/harness/cli/dist/adapters/fs/fake-fs.d.ts +22 -0
- package/harness/cli/dist/adapters/fs/fake-fs.js +63 -0
- package/harness/cli/dist/adapters/fs/fake-fs.js.map +1 -0
- package/harness/cli/dist/adapters/fs/fs-port.d.ts +20 -0
- package/harness/cli/dist/adapters/fs/fs-port.js +2 -0
- package/harness/cli/dist/adapters/fs/fs-port.js.map +1 -0
- package/harness/cli/dist/adapters/fs/node-fs.d.ts +9 -0
- package/harness/cli/dist/adapters/fs/node-fs.js +30 -0
- package/harness/cli/dist/adapters/fs/node-fs.js.map +1 -0
- package/harness/cli/dist/adapters/git/exec-git.d.ts +6 -0
- package/harness/cli/dist/adapters/git/exec-git.js +21 -0
- package/harness/cli/dist/adapters/git/exec-git.js.map +1 -0
- package/harness/cli/dist/adapters/git/fake-git.d.ts +15 -0
- package/harness/cli/dist/adapters/git/fake-git.js +20 -0
- package/harness/cli/dist/adapters/git/fake-git.js.map +1 -0
- package/harness/cli/dist/adapters/git/git-port.d.ts +12 -0
- package/harness/cli/dist/adapters/git/git-port.js +2 -0
- package/harness/cli/dist/adapters/git/git-port.js.map +1 -0
- package/harness/cli/dist/adapters/loader/fake-loader.d.ts +13 -0
- package/harness/cli/dist/adapters/loader/fake-loader.js +25 -0
- package/harness/cli/dist/adapters/loader/fake-loader.js.map +1 -0
- package/harness/cli/dist/adapters/loader/jiti-loader.d.ts +15 -0
- package/harness/cli/dist/adapters/loader/jiti-loader.js +29 -0
- package/harness/cli/dist/adapters/loader/jiti-loader.js.map +1 -0
- package/harness/cli/dist/adapters/loader/module-loader-port.d.ts +12 -0
- package/harness/cli/dist/adapters/loader/module-loader-port.js +2 -0
- package/harness/cli/dist/adapters/loader/module-loader-port.js.map +1 -0
- package/harness/cli/dist/adapters/process/fake-process.d.ts +13 -0
- package/harness/cli/dist/adapters/process/fake-process.js +21 -0
- package/harness/cli/dist/adapters/process/fake-process.js.map +1 -0
- package/harness/cli/dist/adapters/process/node-process.d.ts +6 -0
- package/harness/cli/dist/adapters/process/node-process.js +17 -0
- package/harness/cli/dist/adapters/process/node-process.js.map +1 -0
- package/harness/cli/dist/adapters/process/process-port.d.ts +13 -0
- package/harness/cli/dist/adapters/process/process-port.js +2 -0
- package/harness/cli/dist/adapters/process/process-port.js.map +1 -0
- package/harness/cli/dist/adapters/version-lookup/fake-version-lookup.d.ts +13 -0
- package/harness/cli/dist/adapters/version-lookup/fake-version-lookup.js +21 -0
- package/harness/cli/dist/adapters/version-lookup/fake-version-lookup.js.map +1 -0
- package/harness/cli/dist/adapters/version-lookup/node-version-lookup.d.ts +18 -0
- package/harness/cli/dist/adapters/version-lookup/node-version-lookup.js +51 -0
- package/harness/cli/dist/adapters/version-lookup/node-version-lookup.js.map +1 -0
- package/harness/cli/dist/adapters/version-lookup/version-lookup-port.d.ts +19 -0
- package/harness/cli/dist/adapters/version-lookup/version-lookup-port.js +2 -0
- package/harness/cli/dist/adapters/version-lookup/version-lookup-port.js.map +1 -0
- package/harness/cli/dist/app.d.ts +70 -0
- package/harness/cli/dist/app.js +221 -0
- package/harness/cli/dist/app.js.map +1 -0
- package/harness/cli/dist/index.d.ts +2 -0
- package/harness/cli/dist/index.js +26 -0
- package/harness/cli/dist/index.js.map +1 -0
- package/harness/cli/dist/output/envelope.d.ts +68 -0
- package/harness/cli/dist/output/envelope.js +56 -0
- package/harness/cli/dist/output/envelope.js.map +1 -0
- package/harness/cli/dist/output/error-codes.d.ts +57 -0
- package/harness/cli/dist/output/error-codes.js +57 -0
- package/harness/cli/dist/output/error-codes.js.map +1 -0
- package/harness/cli/dist/output/exit.d.ts +29 -0
- package/harness/cli/dist/output/exit.js +36 -0
- package/harness/cli/dist/output/exit.js.map +1 -0
- package/harness/cli/dist/output/output-port.d.ts +54 -0
- package/harness/cli/dist/output/output-port.js +55 -0
- package/harness/cli/dist/output/output-port.js.map +1 -0
- package/harness/cli/dist/output/style.d.ts +33 -0
- package/harness/cli/dist/output/style.js +68 -0
- package/harness/cli/dist/output/style.js.map +1 -0
- package/harness/cli/dist/services/config/load-config.d.ts +27 -0
- package/harness/cli/dist/services/config/load-config.js +114 -0
- package/harness/cli/dist/services/config/load-config.js.map +1 -0
- package/harness/cli/dist/services/docs/contract.d.ts +41 -0
- package/harness/cli/dist/services/docs/contract.js +14 -0
- package/harness/cli/dist/services/docs/contract.js.map +1 -0
- package/harness/cli/dist/services/docs/docs-content.d.ts +37 -0
- package/harness/cli/dist/services/docs/docs-content.js +48 -0
- package/harness/cli/dist/services/docs/docs-content.js.map +1 -0
- package/harness/cli/dist/services/docs/docs-service.d.ts +26 -0
- package/harness/cli/dist/services/docs/docs-service.js +25 -0
- package/harness/cli/dist/services/docs/docs-service.js.map +1 -0
- package/harness/cli/dist/services/doctor/doctor-service.d.ts +69 -0
- package/harness/cli/dist/services/doctor/doctor-service.js +237 -0
- package/harness/cli/dist/services/doctor/doctor-service.js.map +1 -0
- package/harness/cli/dist/services/extensions/contract.d.ts +138 -0
- package/harness/cli/dist/services/extensions/contract.js +17 -0
- package/harness/cli/dist/services/extensions/contract.js.map +1 -0
- package/harness/cli/dist/services/extensions/discovery.d.ts +53 -0
- package/harness/cli/dist/services/extensions/discovery.js +116 -0
- package/harness/cli/dist/services/extensions/discovery.js.map +1 -0
- package/harness/cli/dist/services/extensions/registry.d.ts +63 -0
- package/harness/cli/dist/services/extensions/registry.js +165 -0
- package/harness/cli/dist/services/extensions/registry.js.map +1 -0
- package/harness/cli/dist/services/extensions/verb-context.d.ts +44 -0
- package/harness/cli/dist/services/extensions/verb-context.js +97 -0
- package/harness/cli/dist/services/extensions/verb-context.js.map +1 -0
- package/harness/cli/dist/services/help/help-service.d.ts +42 -0
- package/harness/cli/dist/services/help/help-service.js +108 -0
- package/harness/cli/dist/services/help/help-service.js.map +1 -0
- package/harness/cli/dist/services/init/governance-template.d.ts +27 -0
- package/harness/cli/dist/services/init/governance-template.js +72 -0
- package/harness/cli/dist/services/init/governance-template.js.map +1 -0
- package/harness/cli/dist/services/init/init-service.d.ts +38 -0
- package/harness/cli/dist/services/init/init-service.js +44 -0
- package/harness/cli/dist/services/init/init-service.js.map +1 -0
- package/harness/cli/dist/services/instructions/core-instructions.d.ts +11 -0
- package/harness/cli/dist/services/instructions/core-instructions.js +80 -0
- package/harness/cli/dist/services/instructions/core-instructions.js.map +1 -0
- package/harness/cli/dist/services/instructions/instructions-service.d.ts +52 -0
- package/harness/cli/dist/services/instructions/instructions-service.js +53 -0
- package/harness/cli/dist/services/instructions/instructions-service.js.map +1 -0
- package/harness/cli/dist/services/observe/buffer-codec.d.ts +51 -0
- package/harness/cli/dist/services/observe/buffer-codec.js +139 -0
- package/harness/cli/dist/services/observe/buffer-codec.js.map +1 -0
- package/harness/cli/dist/services/observe/observe-service.d.ts +87 -0
- package/harness/cli/dist/services/observe/observe-service.js +221 -0
- package/harness/cli/dist/services/observe/observe-service.js.map +1 -0
- package/harness/cli/dist/services/record/contract.d.ts +32 -0
- package/harness/cli/dist/services/record/contract.js +17 -0
- package/harness/cli/dist/services/record/contract.js.map +1 -0
- package/harness/cli/dist/services/record/core-types/retro.d.ts +20 -0
- package/harness/cli/dist/services/record/core-types/retro.js +55 -0
- package/harness/cli/dist/services/record/core-types/retro.js.map +1 -0
- package/harness/cli/dist/services/record/record-service.d.ts +38 -0
- package/harness/cli/dist/services/record/record-service.js +144 -0
- package/harness/cli/dist/services/record/record-service.js.map +1 -0
- package/harness/cli/dist/services/record/registry.d.ts +46 -0
- package/harness/cli/dist/services/record/registry.js +71 -0
- package/harness/cli/dist/services/record/registry.js.map +1 -0
- package/harness/cli/dist/services/scaffold/scaffold-service.d.ts +29 -0
- package/harness/cli/dist/services/scaffold/scaffold-service.js +88 -0
- package/harness/cli/dist/services/scaffold/scaffold-service.js.map +1 -0
- package/harness/cli/dist/services/scaffold/templates.d.ts +42 -0
- package/harness/cli/dist/services/scaffold/templates.js +178 -0
- package/harness/cli/dist/services/scaffold/templates.js.map +1 -0
- package/harness/cli/dist/services/shared/posix-path.d.ts +54 -0
- package/harness/cli/dist/services/shared/posix-path.js +94 -0
- package/harness/cli/dist/services/shared/posix-path.js.map +1 -0
- package/harness/cli/dist/services/shared/temp.d.ts +24 -0
- package/harness/cli/dist/services/shared/temp.js +29 -0
- package/harness/cli/dist/services/shared/temp.js.map +1 -0
- package/harness/cli/dist/services/skills/contract.d.ts +52 -0
- package/harness/cli/dist/services/skills/contract.js +55 -0
- package/harness/cli/dist/services/skills/contract.js.map +1 -0
- package/harness/cli/dist/services/skills/skills-service.d.ts +73 -0
- package/harness/cli/dist/services/skills/skills-service.js +132 -0
- package/harness/cli/dist/services/skills/skills-service.js.map +1 -0
- package/harness/cli/dist/services/update/banner.d.ts +26 -0
- package/harness/cli/dist/services/update/banner.js +28 -0
- package/harness/cli/dist/services/update/banner.js.map +1 -0
- package/harness/cli/dist/services/update/cache.d.ts +21 -0
- package/harness/cli/dist/services/update/cache.js +61 -0
- package/harness/cli/dist/services/update/cache.js.map +1 -0
- package/harness/cli/dist/services/update/constants.d.ts +9 -0
- package/harness/cli/dist/services/update/constants.js +10 -0
- package/harness/cli/dist/services/update/constants.js.map +1 -0
- package/harness/cli/dist/services/update/install.d.ts +26 -0
- package/harness/cli/dist/services/update/install.js +78 -0
- package/harness/cli/dist/services/update/install.js.map +1 -0
- package/harness/cli/dist/services/update/semver.d.ts +16 -0
- package/harness/cli/dist/services/update/semver.js +108 -0
- package/harness/cli/dist/services/update/semver.js.map +1 -0
- package/harness/cli/dist/services/update/update-service.d.ts +46 -0
- package/harness/cli/dist/services/update/update-service.js +91 -0
- package/harness/cli/dist/services/update/update-service.js.map +1 -0
- package/harness/cli/dist/version.d.ts +8 -0
- package/harness/cli/dist/version.js +15 -0
- package/harness/cli/dist/version.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { HarnessRecordType } from '../contract.js';
|
|
2
|
+
/**
|
|
3
|
+
* The core-bundled `retro` record type. Inline TS constant (plan Q-A "lean"):
|
|
4
|
+
* ships cleanly via `npx` with no `package.json#files`/packaging change, mirroring
|
|
5
|
+
* `services/scaffold/templates.ts`.
|
|
6
|
+
*
|
|
7
|
+
* `RETRO_TEMPLATE` is a **deployment echo** of the frozen
|
|
8
|
+
* `skills/eng-harness-loop/eng-harness-4-retro/references/retro.schema.json`: its
|
|
9
|
+
* frontmatter covers the schema's REQUIRED fields (`schema_version`, `retro_id`,
|
|
10
|
+
* `agent`, `started_at`) and uses only the schema's open `system` object. The
|
|
11
|
+
* schema stays the canonical contract; a future `schema?` field can attach it for
|
|
12
|
+
* opt-in validation without changing this type's shape. A unit test
|
|
13
|
+
* (`retro-template.test.ts`) pins the superset so the two can't drift.
|
|
14
|
+
*
|
|
15
|
+
* NOTE: `system.compound.*` below is a documented **convention** inside the
|
|
16
|
+
* schema's open `system` object — NOT a schema-defined field.
|
|
17
|
+
*/
|
|
18
|
+
export declare const RETRO_TEMPLATE = "---\nschema_version: \"1.0\"\nretro_id: \"<ISO8601Z>-<agent>-<hash>\" # e.g. 2026-06-09T09:55:00Z-github-copilot-a8f3\nagent: \"<your-agent-slug>\" # lowercase kebab, e.g. github-copilot\nplan_id: \"<NNN-slug or null>\"\nstarted_at: \"<ISO8601Z>\"\nended_at: \"<ISO8601Z>\"\nsummary: \"<one paragraph: what happened this session>\"\nentries:\n # One block per observation. id = <PREFIX>-<3+ digits>. Any uppercase prefix is valid;\n # DL/MW/GFT/INS/COORD/SUGG/CONF are the recommended per-kind defaults, and run-scoped\n # prefixes (e.g. VF- for a flow worker's own numbering) are equally fine.\n # kind in difficulty | magic-wand | gift | insight | coordination | improvement-suggestion | confusion\n - id: DL-001\n kind: difficulty\n description: \"<>=10 chars - the friction, concretely>\"\n target: tooling # project | tooling | plan | skill | doc | infra | minih | ...\n severity: degrading # blocking | degrading | annoying (for kind: difficulty)\n workaround: \"<what you did to get past it>\"\n suggested_encoding: \"<e.g. justfile recipe wrapping ripgrep>\"\n system:\n compound: # CONVENTION (open 'system' object), not a schema field\n status: open # open | suggested | encoded | wontfix | stale | dismissed\n source: agent-self # user | agent-self\n first_seen_at: \"<ISO8601Z>\"\n---\n\n# Retro \u2014 <plan or session label>\n\n<!-- Optional human narrative. The structured `entries` above are the durable signal. -->\n";
|
|
19
|
+
/** The core `retro` record type (always present, even under \`--no-extensions\`). */
|
|
20
|
+
export declare const retroRecordType: HarnessRecordType;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The core-bundled `retro` record type. Inline TS constant (plan Q-A "lean"):
|
|
3
|
+
* ships cleanly via `npx` with no `package.json#files`/packaging change, mirroring
|
|
4
|
+
* `services/scaffold/templates.ts`.
|
|
5
|
+
*
|
|
6
|
+
* `RETRO_TEMPLATE` is a **deployment echo** of the frozen
|
|
7
|
+
* `skills/eng-harness-loop/eng-harness-4-retro/references/retro.schema.json`: its
|
|
8
|
+
* frontmatter covers the schema's REQUIRED fields (`schema_version`, `retro_id`,
|
|
9
|
+
* `agent`, `started_at`) and uses only the schema's open `system` object. The
|
|
10
|
+
* schema stays the canonical contract; a future `schema?` field can attach it for
|
|
11
|
+
* opt-in validation without changing this type's shape. A unit test
|
|
12
|
+
* (`retro-template.test.ts`) pins the superset so the two can't drift.
|
|
13
|
+
*
|
|
14
|
+
* NOTE: `system.compound.*` below is a documented **convention** inside the
|
|
15
|
+
* schema's open `system` object — NOT a schema-defined field.
|
|
16
|
+
*/
|
|
17
|
+
export const RETRO_TEMPLATE = `---
|
|
18
|
+
schema_version: "1.0"
|
|
19
|
+
retro_id: "<ISO8601Z>-<agent>-<hash>" # e.g. 2026-06-09T09:55:00Z-github-copilot-a8f3
|
|
20
|
+
agent: "<your-agent-slug>" # lowercase kebab, e.g. github-copilot
|
|
21
|
+
plan_id: "<NNN-slug or null>"
|
|
22
|
+
started_at: "<ISO8601Z>"
|
|
23
|
+
ended_at: "<ISO8601Z>"
|
|
24
|
+
summary: "<one paragraph: what happened this session>"
|
|
25
|
+
entries:
|
|
26
|
+
# One block per observation. id = <PREFIX>-<3+ digits>. Any uppercase prefix is valid;
|
|
27
|
+
# DL/MW/GFT/INS/COORD/SUGG/CONF are the recommended per-kind defaults, and run-scoped
|
|
28
|
+
# prefixes (e.g. VF- for a flow worker's own numbering) are equally fine.
|
|
29
|
+
# kind in difficulty | magic-wand | gift | insight | coordination | improvement-suggestion | confusion
|
|
30
|
+
- id: DL-001
|
|
31
|
+
kind: difficulty
|
|
32
|
+
description: "<>=10 chars - the friction, concretely>"
|
|
33
|
+
target: tooling # project | tooling | plan | skill | doc | infra | minih | ...
|
|
34
|
+
severity: degrading # blocking | degrading | annoying (for kind: difficulty)
|
|
35
|
+
workaround: "<what you did to get past it>"
|
|
36
|
+
suggested_encoding: "<e.g. justfile recipe wrapping ripgrep>"
|
|
37
|
+
system:
|
|
38
|
+
compound: # CONVENTION (open 'system' object), not a schema field
|
|
39
|
+
status: open # open | suggested | encoded | wontfix | stale | dismissed
|
|
40
|
+
source: agent-self # user | agent-self
|
|
41
|
+
first_seen_at: "<ISO8601Z>"
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
# Retro — <plan or session label>
|
|
45
|
+
|
|
46
|
+
<!-- Optional human narrative. The structured \`entries\` above are the durable signal. -->
|
|
47
|
+
`;
|
|
48
|
+
/** The core `retro` record type (always present, even under \`--no-extensions\`). */
|
|
49
|
+
export const retroRecordType = {
|
|
50
|
+
kind: 'record',
|
|
51
|
+
type: 'retro',
|
|
52
|
+
description: 'Harness loop retrospective — session friction, gifts, magic-wand wishes.',
|
|
53
|
+
template: RETRO_TEMPLATE,
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=retro.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retro.js","sourceRoot":"","sources":["../../../../src/services/record/core-types/retro.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8B7B,CAAC;AAEF,qFAAqF;AACrF,MAAM,CAAC,MAAM,eAAe,GAAsB;IAChD,IAAI,EAAE,QAAQ;IACd,IAAI,EAAE,OAAO;IACb,WAAW,EAAE,0EAA0E;IACvF,QAAQ,EAAE,cAAc;CACzB,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Clock } from '../../adapters/clock/clock-port.js';
|
|
2
|
+
import type { FsPort } from '../../adapters/fs/fs-port.js';
|
|
3
|
+
import type { ProcessPort } from '../../adapters/process/process-port.js';
|
|
4
|
+
import { ensureTemp } from '../shared/temp.js';
|
|
5
|
+
import type { RecordRegistry } from './registry.js';
|
|
6
|
+
export { ensureTemp };
|
|
7
|
+
export interface RecordDeps {
|
|
8
|
+
fs: FsPort;
|
|
9
|
+
clock: Clock;
|
|
10
|
+
proc: ProcessPort;
|
|
11
|
+
}
|
|
12
|
+
export interface RecordCreateOptions {
|
|
13
|
+
/** The record type (the `<type>` arg). */
|
|
14
|
+
type?: string;
|
|
15
|
+
/** Optional slug for the filename; slugified to `[a-z0-9-]`. Absent → ordinal-only name (`<NNN>.md`). */
|
|
16
|
+
slug?: string;
|
|
17
|
+
}
|
|
18
|
+
export type RecordOutcome = {
|
|
19
|
+
ok: true;
|
|
20
|
+
type: string;
|
|
21
|
+
path: string;
|
|
22
|
+
source: 'core' | 'extension';
|
|
23
|
+
} | {
|
|
24
|
+
ok: false;
|
|
25
|
+
status: 'error' | 'unconfigured';
|
|
26
|
+
code?: string;
|
|
27
|
+
message: string;
|
|
28
|
+
next_action: string;
|
|
29
|
+
};
|
|
30
|
+
/** Lowercase + strip to `[a-z0-9-]`, collapsing/trimming separators. */
|
|
31
|
+
export declare function slugify(raw: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Scaffold a record file for `harness record <type>`. Resolves the type in the
|
|
34
|
+
* merged registry, computes a never-clobbering path, ensures the scratch buffer,
|
|
35
|
+
* and writes the type's template. Returns a typed outcome the act maps onto the
|
|
36
|
+
* Envelope + exit code (ok → 0, unconfigured → 2, error → 1).
|
|
37
|
+
*/
|
|
38
|
+
export declare function createRecord(opts: RecordCreateOptions, registry: RecordRegistry, deps: RecordDeps): RecordOutcome;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { ErrorCodes } from '../../output/error-codes.js';
|
|
2
|
+
import { posixJoin, toPosix } from '../shared/posix-path.js';
|
|
3
|
+
import { ensureTemp, HARNESS_DIR } from '../shared/temp.js';
|
|
4
|
+
// Relocated to services/shared/temp.ts (plan 015 D1); re-exported so existing
|
|
5
|
+
// consumers (and tests) keep their import path.
|
|
6
|
+
export { ensureTemp };
|
|
7
|
+
/**
|
|
8
|
+
* Pure record-scaffolding logic behind injected ports (`fs`/`clock`/`proc`). Like
|
|
9
|
+
* `services/scaffold`, it never imports `node:fs` or `process.cwd()` (Constitution
|
|
10
|
+
* P2), so it is unit-testable with fakes. The CLI owns placement uniformly:
|
|
11
|
+
* `.harness/records/<type>/<YYYY-MM-DD>/<NNN>-<slug>.md` — the UTC date (via the
|
|
12
|
+
* Clock) is a directory and `<NNN>` is a per-day, per-type ordinal (001, 002, …),
|
|
13
|
+
* so records sort chronologically within the day and never clobber an existing file.
|
|
14
|
+
*/
|
|
15
|
+
const RECORDS_DIR = 'records';
|
|
16
|
+
const TYPE_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
17
|
+
/** Max per-day ordinal (keeps `<NNN>` 3 digits); refuse beyond rather than clobber. */
|
|
18
|
+
const MAX_ORDINAL = 999;
|
|
19
|
+
/** Lowercase + strip to `[a-z0-9-]`, collapsing/trimming separators. */
|
|
20
|
+
export function slugify(raw) {
|
|
21
|
+
return raw
|
|
22
|
+
.trim()
|
|
23
|
+
.toLowerCase()
|
|
24
|
+
.replace(/\s+/g, '-')
|
|
25
|
+
.replace(/[^a-z0-9-]+/g, '')
|
|
26
|
+
.replace(/-+/g, '-')
|
|
27
|
+
.replace(/^-|-$/g, '');
|
|
28
|
+
}
|
|
29
|
+
/** `YYYY-MM-DD` from the injected Clock's UTC ISO instant (deterministic in tests). */
|
|
30
|
+
function dateStamp(clock) {
|
|
31
|
+
return clock.nowIso().slice(0, 10);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Next 1-based ordinal for a date dir: 1 + the highest `NNN` prefix already
|
|
35
|
+
* present (files are named `<NNN>[-slug].md`). Missing/empty dir → 1.
|
|
36
|
+
*/
|
|
37
|
+
function nextOrdinal(fs, dateDir) {
|
|
38
|
+
let max = 0;
|
|
39
|
+
for (const name of fs.readdir(dateDir)) {
|
|
40
|
+
const m = /^(\d+)(?:-|\.)/.exec(name);
|
|
41
|
+
if (m) {
|
|
42
|
+
const n = Number.parseInt(m[1], 10);
|
|
43
|
+
if (n > max)
|
|
44
|
+
max = n;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return max + 1;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Scaffold a record file for `harness record <type>`. Resolves the type in the
|
|
51
|
+
* merged registry, computes a never-clobbering path, ensures the scratch buffer,
|
|
52
|
+
* and writes the type's template. Returns a typed outcome the act maps onto the
|
|
53
|
+
* Envelope + exit code (ok → 0, unconfigured → 2, error → 1).
|
|
54
|
+
*/
|
|
55
|
+
export function createRecord(opts, registry, deps) {
|
|
56
|
+
const { fs, clock, proc } = deps;
|
|
57
|
+
// Logical paths are POSIX on every OS (plan 017) — convert once at the boundary.
|
|
58
|
+
const cwd = toPosix(proc.cwd());
|
|
59
|
+
const harnessDir = posixJoin(cwd, HARNESS_DIR);
|
|
60
|
+
// 1. Honest `unconfigured` when there's no harness to record into.
|
|
61
|
+
if (!fs.exists(harnessDir)) {
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
status: 'unconfigured',
|
|
65
|
+
message: `No ${HARNESS_DIR}/ directory found in ${cwd}.`,
|
|
66
|
+
next_action: `No \`${HARNESS_DIR}/\` here — set up the harness first (create \`${HARNESS_DIR}/\`), then re-run.`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// 2. Resolve the type in the merged registry (core ∪ extension).
|
|
70
|
+
const type = opts.type ?? '';
|
|
71
|
+
const known = registry.types.map((t) => t.type);
|
|
72
|
+
const entry = registry.types.find((t) => t.type === type);
|
|
73
|
+
if (!TYPE_PATTERN.test(type) || entry === undefined) {
|
|
74
|
+
return {
|
|
75
|
+
ok: false,
|
|
76
|
+
status: 'error',
|
|
77
|
+
code: ErrorCodes.RECORD_TYPE_UNKNOWN,
|
|
78
|
+
message: `Unknown record type: ${JSON.stringify(type)}.`,
|
|
79
|
+
next_action: `Unknown record type \`${type}\`. Known: ${known.join(', ') || '(none)'} (run \`harness record --list\`).`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
// 3. Optional slug → slugified; an explicit slug that empties out is an error.
|
|
83
|
+
let slug;
|
|
84
|
+
if (opts.slug !== undefined) {
|
|
85
|
+
const cleaned = slugify(opts.slug);
|
|
86
|
+
if (cleaned.length === 0) {
|
|
87
|
+
return {
|
|
88
|
+
ok: false,
|
|
89
|
+
status: 'error',
|
|
90
|
+
code: ErrorCodes.INVALID_ARGS,
|
|
91
|
+
message: `Invalid --slug ${JSON.stringify(opts.slug)} (nothing left after slugify).`,
|
|
92
|
+
next_action: 'Pass a slug of [a-z0-9-], e.g. `--slug my-note`.',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
slug = cleaned;
|
|
96
|
+
}
|
|
97
|
+
// 4. Resolve a never-clobbering path: <date>/<NNN>[-<slug>].md — the date is a
|
|
98
|
+
// directory and <NNN> is a per-day, per-type ordinal = 1 + the highest already
|
|
99
|
+
// present, so a fresh higher ordinal can't collide with an existing record.
|
|
100
|
+
const date = dateStamp(clock);
|
|
101
|
+
const dir = posixJoin(harnessDir, RECORDS_DIR, type, date);
|
|
102
|
+
const fileFor = (ord) => {
|
|
103
|
+
const nnn = String(ord).padStart(3, '0');
|
|
104
|
+
return slug ? `${nnn}-${slug}.md` : `${nnn}.md`;
|
|
105
|
+
};
|
|
106
|
+
let ordinal = nextOrdinal(fs, dir);
|
|
107
|
+
let fileName = fileFor(ordinal);
|
|
108
|
+
// Defensive: if readdir lagged and the computed name somehow exists, bump on.
|
|
109
|
+
while (fs.exists(posixJoin(dir, fileName)) && ordinal < MAX_ORDINAL) {
|
|
110
|
+
ordinal += 1;
|
|
111
|
+
fileName = fileFor(ordinal);
|
|
112
|
+
}
|
|
113
|
+
const fileAbs = posixJoin(dir, fileName);
|
|
114
|
+
const relPath = posixJoin(HARNESS_DIR, RECORDS_DIR, type, date, fileName);
|
|
115
|
+
// 5. Exhaustion guard: refuse rather than clobber (or overflow to 4 digits) at the
|
|
116
|
+
// practically-unreachable limit of MAX_ORDINAL same-day records of this type.
|
|
117
|
+
if (ordinal > MAX_ORDINAL || fs.exists(fileAbs)) {
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
status: 'error',
|
|
121
|
+
code: ErrorCodes.RECORD_WRITE_FAILED,
|
|
122
|
+
message: `Ordinal space exhausted for ${posixJoin(HARNESS_DIR, RECORDS_DIR, type, date)} (${MAX_ORDINAL}+ same-day ${type} records).`,
|
|
123
|
+
next_action: `Too many \`${type}\` records on ${date} — start a new day or prune the folder.`,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// 6. Ensure the scratch buffer (AC-17) + write the template — both under ONE guard so
|
|
127
|
+
// a permissions failure on `.harness/` surfaces as E181 (not a generic E100).
|
|
128
|
+
try {
|
|
129
|
+
ensureTemp(deps);
|
|
130
|
+
fs.mkdirp(dir);
|
|
131
|
+
fs.writeText(fileAbs, entry.template);
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
return {
|
|
135
|
+
ok: false,
|
|
136
|
+
status: 'error',
|
|
137
|
+
code: ErrorCodes.RECORD_WRITE_FAILED,
|
|
138
|
+
message: `Could not write ${relPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
139
|
+
next_action: `Could not write \`${relPath}\` (permissions?). Check \`${HARNESS_DIR}/\` is writable.`,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return { ok: true, type, path: relPath, source: entry.source };
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=record-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record-service.js","sourceRoot":"","sources":["../../../src/services/record/record-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { HarnessRecordType } from './contract.js';
|
|
2
|
+
/** A resolved record type in the merged registry, with its provenance. */
|
|
3
|
+
export interface RecordTypeEntry {
|
|
4
|
+
type: string;
|
|
5
|
+
description: string;
|
|
6
|
+
template: string;
|
|
7
|
+
source: 'core' | 'extension';
|
|
8
|
+
/** Present for `source: 'extension'` — the discovered file the type came from. */
|
|
9
|
+
entryPath?: string;
|
|
10
|
+
}
|
|
11
|
+
/** An extension-provided record type, paired with the file it was discovered in. */
|
|
12
|
+
export interface ExtensionRecordType {
|
|
13
|
+
recordType: HarnessRecordType;
|
|
14
|
+
entryPath: string;
|
|
15
|
+
}
|
|
16
|
+
/** An extension record type dropped because its `type` was already claimed. */
|
|
17
|
+
export interface RecordTypeConflict {
|
|
18
|
+
type: string;
|
|
19
|
+
entryPath: string;
|
|
20
|
+
/** What it collided with: an existing `core` type or an `extension` loaded earlier. */
|
|
21
|
+
shadows: 'core' | 'extension';
|
|
22
|
+
}
|
|
23
|
+
/** The merged record-type surface (core ∪ extension) + the conflicts `doctor` reports. */
|
|
24
|
+
export interface RecordRegistry {
|
|
25
|
+
/** Accepted types, deterministic order: core first, then de-conflicted extensions. */
|
|
26
|
+
types: RecordTypeEntry[];
|
|
27
|
+
/** Extension types skipped because their `type` was already claimed (never fatal). */
|
|
28
|
+
conflicts: RecordTypeConflict[];
|
|
29
|
+
}
|
|
30
|
+
/** The core-bundled record types — always present, even under `--no-extensions`. */
|
|
31
|
+
export declare const coreRecordTypes: HarnessRecordType[];
|
|
32
|
+
/**
|
|
33
|
+
* Field-level issues for a record-type export (empty array = well-formed). Mirrors
|
|
34
|
+
* `verbShapeIssues`: a malformed `kind:'record'` export becomes a clean `E140`
|
|
35
|
+
* (recorded by `doctor`, non-fatal) instead of crashing `harness record`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function recordTypeShapeIssues(value: unknown): string[];
|
|
38
|
+
/**
|
|
39
|
+
* Merge core ∪ extension record types into one registry. Core types are
|
|
40
|
+
* **reserved** — an extension declaring an existing core `type` is recorded as a
|
|
41
|
+
* conflict and the core definition wins; two extensions with the same `type` →
|
|
42
|
+
* first-loaded wins, the other is a conflict. The merge is deterministic and
|
|
43
|
+
* defensive: even if a caller passes un-deconflicted extension types, duplicates
|
|
44
|
+
* are dropped here (the loader normally de-conflicts first). Never throws.
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildRecordRegistry(core?: readonly HarnessRecordType[], extension?: readonly ExtensionRecordType[]): RecordRegistry;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { retroRecordType } from './core-types/retro.js';
|
|
2
|
+
/** Same name rule as verb names — the `<type>` arg + the records subdir name. */
|
|
3
|
+
const TYPE_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
4
|
+
/** The core-bundled record types — always present, even under `--no-extensions`. */
|
|
5
|
+
export const coreRecordTypes = [retroRecordType];
|
|
6
|
+
/**
|
|
7
|
+
* Field-level issues for a record-type export (empty array = well-formed). Mirrors
|
|
8
|
+
* `verbShapeIssues`: a malformed `kind:'record'` export becomes a clean `E140`
|
|
9
|
+
* (recorded by `doctor`, non-fatal) instead of crashing `harness record`.
|
|
10
|
+
*/
|
|
11
|
+
export function recordTypeShapeIssues(value) {
|
|
12
|
+
if (value === null || typeof value !== 'object') {
|
|
13
|
+
return ['not an object'];
|
|
14
|
+
}
|
|
15
|
+
const rt = value;
|
|
16
|
+
const issues = [];
|
|
17
|
+
if (rt.kind !== 'record') {
|
|
18
|
+
issues.push("missing kind:'record'");
|
|
19
|
+
}
|
|
20
|
+
if (typeof rt.type !== 'string' || !TYPE_PATTERN.test(rt.type)) {
|
|
21
|
+
issues.push('missing or invalid type (must match ^[a-z][a-z0-9-]*$)');
|
|
22
|
+
}
|
|
23
|
+
if (typeof rt.description !== 'string' || rt.description.length === 0) {
|
|
24
|
+
issues.push('missing or empty description');
|
|
25
|
+
}
|
|
26
|
+
if (typeof rt.template !== 'string' || rt.template.length === 0) {
|
|
27
|
+
issues.push('missing or empty template');
|
|
28
|
+
}
|
|
29
|
+
return issues;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Merge core ∪ extension record types into one registry. Core types are
|
|
33
|
+
* **reserved** — an extension declaring an existing core `type` is recorded as a
|
|
34
|
+
* conflict and the core definition wins; two extensions with the same `type` →
|
|
35
|
+
* first-loaded wins, the other is a conflict. The merge is deterministic and
|
|
36
|
+
* defensive: even if a caller passes un-deconflicted extension types, duplicates
|
|
37
|
+
* are dropped here (the loader normally de-conflicts first). Never throws.
|
|
38
|
+
*/
|
|
39
|
+
export function buildRecordRegistry(core = coreRecordTypes, extension = []) {
|
|
40
|
+
const types = [];
|
|
41
|
+
const conflicts = [];
|
|
42
|
+
const claimed = new Set();
|
|
43
|
+
const coreNames = new Set(core.map((t) => t.type));
|
|
44
|
+
for (const t of core) {
|
|
45
|
+
if (claimed.has(t.type)) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
claimed.add(t.type);
|
|
49
|
+
types.push({ type: t.type, description: t.description, template: t.template, source: 'core' });
|
|
50
|
+
}
|
|
51
|
+
for (const { recordType: t, entryPath } of extension) {
|
|
52
|
+
if (claimed.has(t.type)) {
|
|
53
|
+
conflicts.push({
|
|
54
|
+
type: t.type,
|
|
55
|
+
entryPath,
|
|
56
|
+
shadows: coreNames.has(t.type) ? 'core' : 'extension',
|
|
57
|
+
});
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
claimed.add(t.type);
|
|
61
|
+
types.push({
|
|
62
|
+
type: t.type,
|
|
63
|
+
description: t.description,
|
|
64
|
+
template: t.template,
|
|
65
|
+
source: 'extension',
|
|
66
|
+
entryPath,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return { types, conflicts };
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/services/record/registry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,iFAAiF;AACjF,MAAM,YAAY,GAAG,mBAAmB,CAAC;AAkCzC,oFAAoF;AACpF,MAAM,CAAC,MAAM,eAAe,GAAwB,CAAC,eAAe,CAAC,CAAC;AAEtE;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAc;IAClD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,OAAO,CAAC,eAAe,CAAC,CAAC;IAC3B,CAAC;IACD,MAAM,EAAE,GAAG,KAAmC,CAAC;IAC/C,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,EAAE,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACzB,MAAM,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;IACvC,CAAC;IACD,IAAI,OAAO,EAAE,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/D,MAAM,CAAC,IAAI,CAAC,wDAAwD,CAAC,CAAC;IACxE,CAAC;IACD,IAAI,OAAO,EAAE,CAAC,WAAW,KAAK,QAAQ,IAAI,EAAE,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtE,MAAM,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;IAC9C,CAAC;IACD,IAAI,OAAO,EAAE,CAAC,QAAQ,KAAK,QAAQ,IAAI,EAAE,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAAqC,eAAe,EACpD,YAA4C,EAAE;IAE9C,MAAM,KAAK,GAAsB,EAAE,CAAC;IACpC,MAAM,SAAS,GAAyB,EAAE,CAAC;IAC3C,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAEnD,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YACxB,SAAS;QACX,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACpB,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACjG,CAAC;IAED,KAAK,MAAM,EAAE,UAAU,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,SAAS,EAAE,CAAC;QACrD,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YACxB,SAAS,CAAC,IAAI,CAAC;gBACb,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,SAAS;gBACT,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW;aACtD,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACpB,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,MAAM,EAAE,WAAW;YACnB,SAAS;SACV,CAAC,CAAC;IACL,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { FsPort } from '../../adapters/fs/fs-port.js';
|
|
2
|
+
import type { ProcessPort } from '../../adapters/process/process-port.js';
|
|
3
|
+
import { type ScaffoldVariant } from './templates.js';
|
|
4
|
+
export interface ScaffoldOptions {
|
|
5
|
+
name: string;
|
|
6
|
+
wrap?: string;
|
|
7
|
+
js?: boolean;
|
|
8
|
+
force?: boolean;
|
|
9
|
+
/** Scaffold a record-type extension (`kind:'record'`) instead of a verb. */
|
|
10
|
+
record?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export type ScaffoldOutcome = {
|
|
13
|
+
ok: true;
|
|
14
|
+
/** Relative path of the entry file (`.harness/extensions/<name>/extension.<ext>`). */
|
|
15
|
+
path: string;
|
|
16
|
+
/** Relative path of the starter briefing (`.harness/extensions/<name>/instructions.md`). */
|
|
17
|
+
instructionsPath: string;
|
|
18
|
+
verb: string;
|
|
19
|
+
variant: ScaffoldVariant;
|
|
20
|
+
} | {
|
|
21
|
+
ok: false;
|
|
22
|
+
code: string;
|
|
23
|
+
message: string;
|
|
24
|
+
next_action: string;
|
|
25
|
+
};
|
|
26
|
+
export declare function scaffoldExtension(opts: ScaffoldOptions, deps: {
|
|
27
|
+
fs: FsPort;
|
|
28
|
+
proc: ProcessPort;
|
|
29
|
+
}): ScaffoldOutcome;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { ErrorCodes } from '../../output/error-codes.js';
|
|
2
|
+
import { RESERVED_NAMES } from '../extensions/registry.js';
|
|
3
|
+
import { posixJoin, toPosix } from '../shared/posix-path.js';
|
|
4
|
+
import { renderStarter, starterInstructions } from './templates.js';
|
|
5
|
+
/**
|
|
6
|
+
* Scaffold a new extension PACKAGE for `harness new` (plan 006; folder form
|
|
7
|
+
* since plan 014 AC-8): `<name>/extension.<ext>` + a starter `instructions.md`
|
|
8
|
+
* beside it. Pure harness logic behind injected ports: validates the verb name,
|
|
9
|
+
* roots the target the SAME way discovery does
|
|
10
|
+
* (`posixJoin(toPosix(proc.cwd()), '.harness', 'extensions', …)` — Finding 07;
|
|
11
|
+
* logical paths are POSIX on every OS, plan 017), picks a starter template, and
|
|
12
|
+
* writes via `FsPort`. Never imports `node:fs` or `process.cwd()` (Constitution
|
|
13
|
+
* P2), so it is unit-testable with fakes.
|
|
14
|
+
*/
|
|
15
|
+
const EXTENSIONS_DIR = ['.harness', 'extensions'];
|
|
16
|
+
// Hyphen-separated alphanumeric segments — no trailing/doubled hyphens, since
|
|
17
|
+
// the name is now also a directory name (companion F002).
|
|
18
|
+
const NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
19
|
+
// A --wrap value is embedded verbatim into the generated file. v1 supports a
|
|
20
|
+
// simple `cmd arg arg` line only; reject anything that could break the emitted
|
|
21
|
+
// JS (quotes, backticks, `$`, backslash, shell operators) rather than write
|
|
22
|
+
// invalid code (F002). Quoting/operators are a documented v1 non-goal.
|
|
23
|
+
const SAFE_WRAP_PATTERN = /^[\w\-./:= ]+$/;
|
|
24
|
+
export function scaffoldExtension(opts, deps) {
|
|
25
|
+
const { name, wrap, js = false, force = false, record = false } = opts;
|
|
26
|
+
const { fs, proc } = deps;
|
|
27
|
+
if (!NAME_PATTERN.test(name)) {
|
|
28
|
+
return {
|
|
29
|
+
ok: false,
|
|
30
|
+
code: ErrorCodes.SCAFFOLD_INVALID_NAME,
|
|
31
|
+
message: `Invalid extension name: ${JSON.stringify(name)}`,
|
|
32
|
+
next_action: 'Use a lowercase, hyphenated name starting with a letter, e.g. `harness new my-verb`.',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (RESERVED_NAMES.has(name)) {
|
|
36
|
+
return {
|
|
37
|
+
ok: false,
|
|
38
|
+
code: ErrorCodes.SCAFFOLD_NAME_RESERVED,
|
|
39
|
+
message: `'${name}' is a reserved core command and cannot be an extension verb.`,
|
|
40
|
+
next_action: `Choose a different verb name (reserved: ${[...RESERVED_NAMES].join(', ')}).`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (!record &&
|
|
44
|
+
wrap !== undefined &&
|
|
45
|
+
(wrap.trim().length === 0 || !SAFE_WRAP_PATTERN.test(wrap))) {
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
code: ErrorCodes.INVALID_ARGS,
|
|
49
|
+
message: `Unsupported --wrap command: ${JSON.stringify(wrap)}`,
|
|
50
|
+
next_action: 'Use a simple "cmd arg arg" form (letters, digits, - _ . / : =). For quotes/operators, scaffold without --wrap and edit the generated run() by hand.',
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const { contents, variant, ext } = renderStarter({ name, js, wrap, record });
|
|
54
|
+
// Folder form (plan 014 AC-8): every variant lands at <name>/extension.<ext>
|
|
55
|
+
// (the `.record.ts` filename convention is retired — routing is by `kind`).
|
|
56
|
+
const fileName = `extension.${ext}`;
|
|
57
|
+
const relPath = posixJoin(...EXTENSIONS_DIR, name, fileName);
|
|
58
|
+
const relInstructions = posixJoin(...EXTENSIONS_DIR, name, 'instructions.md');
|
|
59
|
+
const dirAbs = posixJoin(toPosix(proc.cwd()), ...EXTENSIONS_DIR, name);
|
|
60
|
+
const fileAbs = posixJoin(dirAbs, fileName);
|
|
61
|
+
const instructionsAbs = posixJoin(dirAbs, 'instructions.md');
|
|
62
|
+
if (!force && fs.exists(fileAbs)) {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
code: ErrorCodes.SCAFFOLD_FILE_EXISTS,
|
|
66
|
+
message: `An extension already exists at ${relPath}.`,
|
|
67
|
+
next_action: 'Pass --force to overwrite, or choose another name.',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
fs.mkdirp(dirAbs);
|
|
72
|
+
fs.writeText(fileAbs, contents);
|
|
73
|
+
// Never clobber an authored briefing — --force replaces code, not judgment.
|
|
74
|
+
if (!fs.exists(instructionsAbs)) {
|
|
75
|
+
fs.writeText(instructionsAbs, starterInstructions(name));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
return {
|
|
80
|
+
ok: false,
|
|
81
|
+
code: ErrorCodes.SCAFFOLD_WRITE_FAILED,
|
|
82
|
+
message: `Could not write ${relPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
83
|
+
next_action: 'Check directory permissions and retry.',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return { ok: true, path: relPath, instructionsPath: relInstructions, verb: name, variant };
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=scaffold-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold-service.js","sourceRoot":"","sources":["../../../src/services/scaffold/scaffold-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure string builders for the scaffolded extension starters (`harness new`,
|
|
3
|
+
* plan 006). Output is byte-exact to workshop 001 §4 so it can be asserted in
|
|
4
|
+
* tests and survives `npx`/bundling (no loose template files to resolve at
|
|
5
|
+
* runtime). Authors never see this module — only the file it writes.
|
|
6
|
+
*/
|
|
7
|
+
export type ScaffoldVariant = 'minimal-ts' | 'minimal-js' | 'wrap-ts' | 'wrap-js' | 'record-ts';
|
|
8
|
+
/** kebab/lower verb name → a valid camelCase JS identifier for the local const. */
|
|
9
|
+
export declare function toIdentifier(name: string): string;
|
|
10
|
+
/** Minimal TypeScript starter — an honest `unconfigured` stub (workshop §4a). */
|
|
11
|
+
export declare function minimalTs(name: string): string;
|
|
12
|
+
/** Wrap-a-real-command TypeScript starter (workshop §4b). */
|
|
13
|
+
export declare function wrapTs(name: string, command: string): string;
|
|
14
|
+
/** Minimal plain-JS starter — JSDoc contract reference, no runtime import (workshop §4c). */
|
|
15
|
+
export declare function minimalJs(name: string): string;
|
|
16
|
+
/** Wrap-a-real-command plain-JS starter (workshop §4d). */
|
|
17
|
+
export declare function wrapJs(name: string, command: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Record-type extension starter (`harness new <name> --record`). Exports a
|
|
20
|
+
* {@link HarnessRecordType} the loader discovers by its `kind:'record'`. The
|
|
21
|
+
* record's "schema" lives in the `template` body (frontmatter + comments); the
|
|
22
|
+
* CLI is schema-agnostic, so editing the template is all it takes to design a type.
|
|
23
|
+
*/
|
|
24
|
+
export declare function recordTs(name: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Starter `instructions.md` for a scaffolded extension package (plan 014 AC-8).
|
|
27
|
+
* A guided TODO addressed to the CALLING agent: what the verb computes
|
|
28
|
+
* deterministically, and what judgment it expects back. Served verbatim by
|
|
29
|
+
* `harness instructions <name>` once authored.
|
|
30
|
+
*/
|
|
31
|
+
export declare function starterInstructions(name: string): string;
|
|
32
|
+
/** Pick the starter + extension from the `harness new` flags. */
|
|
33
|
+
export declare function renderStarter(opts: {
|
|
34
|
+
name: string;
|
|
35
|
+
js: boolean;
|
|
36
|
+
wrap?: string;
|
|
37
|
+
record?: boolean;
|
|
38
|
+
}): {
|
|
39
|
+
contents: string;
|
|
40
|
+
variant: ScaffoldVariant;
|
|
41
|
+
ext: 'ts' | 'js';
|
|
42
|
+
};
|