project-tiny-context-harness 0.2.39

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.
Files changed (86) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +399 -0
  3. package/assets/README.md +455 -0
  4. package/assets/README.zh-CN.md +131 -0
  5. package/assets/agents/.gitkeep +1 -0
  6. package/assets/agents/AGENTS_CORE.md +58 -0
  7. package/assets/context_templates/architecture.md +31 -0
  8. package/assets/context_templates/area.md +31 -0
  9. package/assets/context_templates/context.toml +27 -0
  10. package/assets/context_templates/deployment.md +35 -0
  11. package/assets/context_templates/global.md +53 -0
  12. package/assets/context_templates/verification.md +31 -0
  13. package/assets/github/.gitkeep +1 -0
  14. package/assets/github/harness.yml +37 -0
  15. package/assets/make/.gitkeep +1 -0
  16. package/assets/make/sdlc-harness.mk +39 -0
  17. package/assets/skills/context_development_engineer/SKILL.md +86 -0
  18. package/assets/skills/context_full_project_export/SKILL.md +55 -0
  19. package/assets/skills/context_product_plan/SKILL.md +85 -0
  20. package/assets/skills/context_uiux_design/SKILL.md +110 -0
  21. package/assets/tools/validate_context.py +276 -0
  22. package/dist/cli.d.ts +2 -0
  23. package/dist/cli.js +12 -0
  24. package/dist/commands/doctor.d.ts +1 -0
  25. package/dist/commands/doctor.js +16 -0
  26. package/dist/commands/export-context.d.ts +1 -0
  27. package/dist/commands/export-context.js +149 -0
  28. package/dist/commands/index.d.ts +3 -0
  29. package/dist/commands/index.js +33 -0
  30. package/dist/commands/init.d.ts +3 -0
  31. package/dist/commands/init.js +108 -0
  32. package/dist/commands/package-source.d.ts +1 -0
  33. package/dist/commands/package-source.js +24 -0
  34. package/dist/commands/sync.d.ts +1 -0
  35. package/dist/commands/sync.js +14 -0
  36. package/dist/commands/upgrade.d.ts +1 -0
  37. package/dist/commands/upgrade.js +7 -0
  38. package/dist/commands/validate.d.ts +1 -0
  39. package/dist/commands/validate.js +14 -0
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.js +1 -0
  42. package/dist/lib/config.d.ts +5 -0
  43. package/dist/lib/config.js +52 -0
  44. package/dist/lib/constants.d.ts +3 -0
  45. package/dist/lib/constants.js +3 -0
  46. package/dist/lib/context-export.d.ts +21 -0
  47. package/dist/lib/context-export.js +845 -0
  48. package/dist/lib/context-manifest.d.ts +3 -0
  49. package/dist/lib/context-manifest.js +103 -0
  50. package/dist/lib/context-templates.d.ts +5 -0
  51. package/dist/lib/context-templates.js +204 -0
  52. package/dist/lib/design-md.d.ts +2 -0
  53. package/dist/lib/design-md.js +132 -0
  54. package/dist/lib/doctor.d.ts +6 -0
  55. package/dist/lib/doctor.js +41 -0
  56. package/dist/lib/fs.d.ts +8 -0
  57. package/dist/lib/fs.js +56 -0
  58. package/dist/lib/harness-root.d.ts +9 -0
  59. package/dist/lib/harness-root.js +50 -0
  60. package/dist/lib/init.d.ts +5 -0
  61. package/dist/lib/init.js +65 -0
  62. package/dist/lib/managed-file.d.ts +19 -0
  63. package/dist/lib/managed-file.js +21 -0
  64. package/dist/lib/migrations.d.ts +11 -0
  65. package/dist/lib/migrations.js +180 -0
  66. package/dist/lib/package-json-config.d.ts +2 -0
  67. package/dist/lib/package-json-config.js +37 -0
  68. package/dist/lib/package-source.d.ts +8 -0
  69. package/dist/lib/package-source.js +124 -0
  70. package/dist/lib/paths.d.ts +5 -0
  71. package/dist/lib/paths.js +11 -0
  72. package/dist/lib/schema-guard.d.ts +3 -0
  73. package/dist/lib/schema-guard.js +28 -0
  74. package/dist/lib/sync-engine.d.ts +7 -0
  75. package/dist/lib/sync-engine.js +350 -0
  76. package/dist/lib/types.d.ts +21 -0
  77. package/dist/lib/types.js +1 -0
  78. package/dist/lib/upgrade.d.ts +1 -0
  79. package/dist/lib/upgrade.js +21 -0
  80. package/dist/lib/validators.d.ts +5 -0
  81. package/dist/lib/validators.js +459 -0
  82. package/dist/lib/yaml.d.ts +2 -0
  83. package/dist/lib/yaml.js +7 -0
  84. package/migrations/README.md +3 -0
  85. package/package.json +68 -0
  86. package/source-mappings.yaml +25 -0
@@ -0,0 +1,3 @@
1
+ export declare const CONTEXT_MANIFEST_PATH = "project_context/context.toml";
2
+ export declare function defaultContextManifestTemplate(): string;
3
+ export declare function contextManifestFromExistingAreas(projectRoot: string): Promise<string>;
@@ -0,0 +1,103 @@
1
+ import path from "node:path";
2
+ import { listFiles } from "./fs.js";
3
+ export const CONTEXT_MANIFEST_PATH = "project_context/context.toml";
4
+ export function defaultContextManifestTemplate() {
5
+ return [
6
+ "# Schema v4 Minimal Context graph manifest.",
7
+ "# Keep the default product/domain area for ordinary projects. Role context nodes",
8
+ "# are read-purpose slices owned by an area or, only when cross-domain, by the project root.",
9
+ "# When migrating deep files under project_context/areas/**, refine obvious",
10
+ "# contract/foundation/subdomain/verification/deployment/implementation-index/",
11
+ "# decision-rationale/archive files into [[context]] entries instead of keeping",
12
+ "# every Markdown file as an [[areas]] product owner.",
13
+ "",
14
+ "[[areas]]",
15
+ "id = \"main\"",
16
+ "root = \".\"",
17
+ "context = \"project_context/areas/main.md\"",
18
+ "kind = \"app\"",
19
+ "default = true",
20
+ "",
21
+ "[[context]]",
22
+ "path = \"project_context/areas/main/verification.md\"",
23
+ "role = \"verification\"",
24
+ "read_policy = \"default\"",
25
+ "triggers = [\"test\", \"verify\", \"verification\", \"smoke\", \"ci\"]",
26
+ "",
27
+ "# Example optional node:",
28
+ "# [[context]]",
29
+ "# path = \"project_context/areas/main/deployment.md\"",
30
+ "# role = \"deployment\"",
31
+ "# read_policy = \"on-demand\"",
32
+ "# triggers = [\"deploy\", \"deployment\", \"runtime\", \"cloud\", \"docker\"]",
33
+ ""
34
+ ].join("\n");
35
+ }
36
+ export async function contextManifestFromExistingAreas(projectRoot) {
37
+ const areasRoot = path.join(projectRoot, "project_context", "areas");
38
+ const areas = (await listFiles(areasRoot))
39
+ .filter((file) => file.endsWith(".md"))
40
+ .sort();
41
+ if (areas.length === 0) {
42
+ return defaultContextManifestTemplate();
43
+ }
44
+ const lines = [
45
+ "# Schema v4 Minimal Context graph manifest.",
46
+ "# Auto-created by upgrade from existing project_context/areas/**/*.md files.",
47
+ "# Review deep or non-area context and move it to explicit [[context]] role entries",
48
+ "# such as subdomain, contract, foundation, verification, deployment, archive,",
49
+ "# implementation-index or decision-rationale when needed.",
50
+ ""
51
+ ];
52
+ const areaEntries = [];
53
+ const contextEntries = [];
54
+ for (const areaPath of areas) {
55
+ const relativeToAreas = path.relative(areasRoot, areaPath).split(path.sep).join("/");
56
+ const contextPath = `project_context/areas/${relativeToAreas}`;
57
+ const id = contextUnitId(relativeToAreas);
58
+ const role = inferredRoleContext(relativeToAreas);
59
+ if (role) {
60
+ contextEntries.push({
61
+ path: contextPath,
62
+ role,
63
+ triggers: role === "verification"
64
+ ? ["test", "verify", "verification", "smoke", "ci"]
65
+ : ["deploy", "deployment", "runtime", "cloud", "docker"]
66
+ });
67
+ }
68
+ else {
69
+ areaEntries.push({ contextPath, id });
70
+ }
71
+ }
72
+ const defaultId = areaEntries.some((entry) => entry.id === "main") ? "main" : areaEntries[0]?.id;
73
+ for (const { contextPath, id } of areaEntries) {
74
+ lines.push("[[areas]]", `id = "${id}"`, "root = \".\"", `context = "${contextPath}"`, `kind = "${id === "main" ? "app" : "context-unit"}"`, id === defaultId ? "default = true" : "default = false", "");
75
+ }
76
+ for (const context of contextEntries) {
77
+ lines.push("[[context]]", `path = "${context.path}"`, `role = "${context.role}"`, context.role === "verification" ? "read_policy = \"default\"" : "read_policy = \"on-demand\"", `triggers = [${context.triggers.map((trigger) => `"${trigger}"`).join(", ")}]`, "");
78
+ }
79
+ return lines.join("\n");
80
+ }
81
+ function inferredRoleContext(relativeToAreas) {
82
+ const normalized = relativeToAreas.toLowerCase();
83
+ if (normalized.endsWith("/verification.md") || normalized === "verification.md") {
84
+ return "verification";
85
+ }
86
+ if (normalized.endsWith("/deployment.md") || normalized === "deployment.md") {
87
+ return "deployment";
88
+ }
89
+ return undefined;
90
+ }
91
+ function contextUnitId(relativeToAreas) {
92
+ const withoutExtension = relativeToAreas.replace(/\.md$/i, "");
93
+ const parts = withoutExtension.split("/");
94
+ const last = parts.at(-1)?.toLowerCase();
95
+ const semanticParts = last === "readme" || last === "index" ? parts.slice(0, -1) : parts;
96
+ const raw = (semanticParts.length > 0 ? semanticParts : ["main"]).join("-");
97
+ const slug = raw
98
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
99
+ .toLowerCase()
100
+ .replace(/[^a-z0-9]+/g, "-")
101
+ .replace(/^-+|-+$/g, "");
102
+ return slug || "main";
103
+ }
@@ -0,0 +1,5 @@
1
+ export declare function globalContextTemplate(): string;
2
+ export declare function architectureContextTemplate(): string;
3
+ export declare function areaContextTemplate(areaName: string): string;
4
+ export declare function verificationContextTemplate(areaName: string): string;
5
+ export declare function deploymentContextTemplate(areaName: string): string;
@@ -0,0 +1,204 @@
1
+ export function globalContextTemplate() {
2
+ return [
3
+ "# Project / Delivery Context",
4
+ "",
5
+ "## Project Goal",
6
+ "",
7
+ "- Describe the user-visible goal this project is trying to achieve.",
8
+ "",
9
+ "## Non-goals / Boundaries",
10
+ "",
11
+ "- List what this project intentionally does not do.",
12
+ "",
13
+ "## Background",
14
+ "",
15
+ "- Capture the minimum background a fresh agent needs before changing code.",
16
+ "",
17
+ "## Design Rationale",
18
+ "",
19
+ "- Record only durable choices that are hard to infer from code or tests. Classify changes before implementation; if a change alters product ownership/plans, module responsibilities, information architecture, API/Schema, state/scheduler semantics, cross-area boundaries, verification role paths or deployment role paths, update Context before code with enough durable context to guide implementation.",
20
+ "",
21
+ "## Architecture Context",
22
+ "",
23
+ "- Link to `project_context/architecture.md`; keep architecture notes minimal and focused on boundaries, components and constraints that are not obvious from code.",
24
+ "",
25
+ "## Context Graph",
26
+ "",
27
+ "- Link to `project_context/context.toml` and keep its default area, role, trigger, read policy and boundary metadata aligned with this Context.",
28
+ "- When adding or reorganizing files under `project_context/areas/**`, run a soft role placement scan before registering every Markdown file as an area: product ownership stays in `area` / `domain` / `subdomain`; contracts, foundations, verification, deployment, implementation indexes, decision rationale and archives should use role Context when that better fits the reading purpose.",
29
+ "",
30
+ "## Product / Delivery Brief",
31
+ "",
32
+ "- Capture durable product goals, users, core flows, acceptance signals and non-goals.",
33
+ "",
34
+ "## UX / Screen Brief",
35
+ "",
36
+ "- Capture durable screen, flow, interaction, responsive and accessibility facts. Use `DESIGN.md` for visual identity and design tokens when needed.",
37
+ "",
38
+ "## Verification Entry Points",
39
+ "",
40
+ "- Point to the default verification context for repeatable test, smoke, CI or validation paths.",
41
+ "- Project-level cross-domain verification may live here only as a short index; execution details belong in `verification` role Context.",
42
+ "",
43
+ "## Current State",
44
+ "",
45
+ "- Summarize what is currently implemented or intentionally blocked.",
46
+ "",
47
+ "## Next Safe Action",
48
+ "",
49
+ "- State the safest next step for a fresh agent, including whether the next change should update Context before code.",
50
+ "",
51
+ "## Context Index",
52
+ "",
53
+ "- [main](areas/main.md)",
54
+ "- [main verification](areas/main/verification.md)",
55
+ ""
56
+ ].join("\n");
57
+ }
58
+ export function architectureContextTemplate() {
59
+ return [
60
+ "# Architecture Context",
61
+ "",
62
+ "This is the restrained architecture context. Keep only facts that help a fresh agent recover system shape, boundaries and durable constraints quickly.",
63
+ "",
64
+ "## System Boundary",
65
+ "",
66
+ "- Describe what is inside this project and what external systems, providers or runtime assumptions sit outside it.",
67
+ "",
68
+ "## Component Map",
69
+ "",
70
+ "- List the smallest useful set of components, areas or context units and how they relate.",
71
+ "",
72
+ "## Data / Control Flow",
73
+ "",
74
+ "- Summarize only the durable request, event, state or data flow that is hard to infer from code alone.",
75
+ "",
76
+ "## Design Rationale",
77
+ "",
78
+ "- Record architecture-level choices that still constrain future work; architecture boundary changes should be captured here before implementation alignment.",
79
+ "",
80
+ "## Constraints And Tradeoffs",
81
+ "",
82
+ "- Capture performance, safety, integration, deployment or maintainability constraints that matter for future changes.",
83
+ "",
84
+ "## Verification Implications",
85
+ "",
86
+ "- List project-specific verification entry points affected by architectural changes; do not claim tests already passed.",
87
+ "",
88
+ "## Open Risks",
89
+ "",
90
+ "- List unresolved architectural risks or unknowns.",
91
+ ""
92
+ ].join("\n");
93
+ }
94
+ export function areaContextTemplate(areaName) {
95
+ return [
96
+ `# Area Context: ${areaName}`,
97
+ "",
98
+ "## Responsibility",
99
+ "",
100
+ "- Describe this product/domain area or context unit's responsibility.",
101
+ "",
102
+ "## User / System Contract",
103
+ "",
104
+ "- Describe the external behavior, API, CLI, UI, screen state, interaction or data contract. Contract changes should be captured here before implementation alignment.",
105
+ "",
106
+ "## Core Data / API / State",
107
+ "",
108
+ "- Summarize the important data structures, APIs, state transitions, or rules.",
109
+ "",
110
+ "## Key Constraints",
111
+ "",
112
+ "- List constraints that are not obvious from code alone, including product rules, responsive/a11y needs or visual boundaries.",
113
+ "",
114
+ "## Code Entry Points",
115
+ "",
116
+ "- `src/` or the concrete file/function entry points.",
117
+ "",
118
+ "## Related Role Context",
119
+ "",
120
+ "- Verification paths live in this area's `verification` role Context, such as `project_context/areas/main/verification.md`.",
121
+ "- Deployment/runtime/bootstrap paths live in this area's optional `deployment` role Context when those facts exist.",
122
+ "",
123
+ "## Open Risks",
124
+ "",
125
+ "- List unresolved risks or blockers.",
126
+ ""
127
+ ].join("\n");
128
+ }
129
+ export function verificationContextTemplate(areaName) {
130
+ return [
131
+ `# Verification Context: ${areaName}`,
132
+ "",
133
+ "This role Context records critical repeat-execution paths for the owning area. Keep it minimal: enough for a future agent to rerun verification without rediscovering setup, not a test report.",
134
+ "",
135
+ "## Owner",
136
+ "",
137
+ `- Owning area: \`${areaName}\`.`,
138
+ "",
139
+ "## Verification Paths",
140
+ "",
141
+ "- `npm test` or the shortest project-specific test, smoke, CI, probe or validation command.",
142
+ "",
143
+ "## Required Preparation",
144
+ "",
145
+ "- List only durable setup such as services, env files, fixtures, local runtimes or external dependencies needed before rerun.",
146
+ "",
147
+ "## Expected Signals",
148
+ "",
149
+ "- Name the stage, health check, status, artifact shape or observable signal that means the path reached the intended point.",
150
+ "",
151
+ "## Acceptable Warnings",
152
+ "",
153
+ "- List warnings that are expected and should not trigger repeated investigation.",
154
+ "",
155
+ "## Excluded Dead Ends",
156
+ "",
157
+ "- List previously ruled-out commands, providers, endpoints or setup paths only when remembering them prevents repeated wasted work.",
158
+ "",
159
+ "## Forbidden Content",
160
+ "",
161
+ "- Do not record one-off logs, full command output, temporary JSON, CI artifacts, test reports, secrets, tokens, cookies, device ids, raw payloads or pass/fail claims.",
162
+ ""
163
+ ].join("\n");
164
+ }
165
+ export function deploymentContextTemplate(areaName) {
166
+ return [
167
+ `# Deployment Context: ${areaName}`,
168
+ "",
169
+ "This optional role Context records critical repeat-execution paths for deploy, runtime bootstrap, cloud initialization and operational recovery. Keep it minimal and durable.",
170
+ "",
171
+ "## Owner",
172
+ "",
173
+ `- Owning area: \`${areaName}\`.`,
174
+ "",
175
+ "## Runtime Topology",
176
+ "",
177
+ "- List durable service distribution only when it matters for rerunning deploy or bootstrap, such as Web/API/worker/Redis/DB/Docker/cloud instance boundaries.",
178
+ "",
179
+ "## Deployment Paths",
180
+ "",
181
+ "- List the shortest deploy, CI/CD, bootstrap, migration, health-check or rollback/degradation command/path.",
182
+ "",
183
+ "## Required Preparation",
184
+ "",
185
+ "- List only durable setup such as cloud resources, env files, compose profiles, secret mounting names or database initialization steps. Do not store secret values.",
186
+ "",
187
+ "## Expected Signals",
188
+ "",
189
+ "- Name health checks, status transitions, URLs, queues, containers or logs-at-a-glance that show the path reached the intended stage.",
190
+ "",
191
+ "## Acceptable Warnings",
192
+ "",
193
+ "- List known benign warnings or slow-start states.",
194
+ "",
195
+ "## Excluded Dead Ends",
196
+ "",
197
+ "- List previously ruled-out deploy/bootstrap paths only when remembering them prevents repeated wasted work.",
198
+ "",
199
+ "## Forbidden Content",
200
+ "",
201
+ "- Do not record one-off logs, full command output, CI artifacts, release ledgers, secrets, tokens, cookies, device ids, raw payloads or claims that deployment already succeeded.",
202
+ ""
203
+ ].join("\n");
204
+ }
@@ -0,0 +1,2 @@
1
+ export declare const DESIGN_MD_PATH = "DESIGN.md";
2
+ export declare function createDesignMdIfMissing(projectRoot: string): Promise<boolean>;
@@ -0,0 +1,132 @@
1
+ import path from "node:path";
2
+ import { pathExists, writeTextIfChanged } from "./fs.js";
3
+ export const DESIGN_MD_PATH = "DESIGN.md";
4
+ export async function createDesignMdIfMissing(projectRoot) {
5
+ const target = path.join(projectRoot, DESIGN_MD_PATH);
6
+ if (await pathExists(target)) {
7
+ return false;
8
+ }
9
+ return writeTextIfChanged(target, designMdTemplate());
10
+ }
11
+ function designMdTemplate() {
12
+ return [
13
+ "---",
14
+ 'version: "alpha"',
15
+ 'name: "Starter Design System"',
16
+ 'description: "Neutral baseline design guidance for projects that have not defined their own visual system."',
17
+ "colors:",
18
+ ' canvas: "#F8FAFC"',
19
+ ' surface: "#FFFFFF"',
20
+ ' surface-muted: "#EEF2F7"',
21
+ ' text: "#172033"',
22
+ ' text-muted: "#5D6B82"',
23
+ ' primary: "#2563EB"',
24
+ ' primary-hover: "#1D4ED8"',
25
+ ' on-primary: "#FFFFFF"',
26
+ "typography:",
27
+ " display:",
28
+ ' fontFamily: "Public Sans"',
29
+ ' fontSize: "2.5rem"',
30
+ " fontWeight: 700",
31
+ " lineHeight: 1.1",
32
+ " title:",
33
+ ' fontFamily: "Public Sans"',
34
+ ' fontSize: "1.5rem"',
35
+ " fontWeight: 700",
36
+ " lineHeight: 1.25",
37
+ " body:",
38
+ ' fontFamily: "Public Sans"',
39
+ ' fontSize: "1rem"',
40
+ " fontWeight: 400",
41
+ " lineHeight: 1.6",
42
+ " label:",
43
+ ' fontFamily: "Public Sans"',
44
+ ' fontSize: "0.875rem"',
45
+ " fontWeight: 600",
46
+ " lineHeight: 1.3",
47
+ "rounded:",
48
+ " sm: 4px",
49
+ " md: 8px",
50
+ " lg: 12px",
51
+ "spacing:",
52
+ " xs: 4px",
53
+ " sm: 8px",
54
+ " md: 16px",
55
+ " lg: 24px",
56
+ " xl: 32px",
57
+ "components:",
58
+ " app-shell:",
59
+ ' backgroundColor: "{colors.canvas}"',
60
+ ' textColor: "{colors.text}"',
61
+ " surface-card:",
62
+ ' backgroundColor: "{colors.surface}"',
63
+ ' textColor: "{colors.text}"',
64
+ ' rounded: "{rounded.md}"',
65
+ " padding: 16px",
66
+ " quiet-control:",
67
+ ' backgroundColor: "{colors.surface-muted}"',
68
+ ' textColor: "{colors.text-muted}"',
69
+ ' rounded: "{rounded.sm}"',
70
+ " padding: 8px",
71
+ " primary-action:",
72
+ ' backgroundColor: "{colors.primary}"',
73
+ ' textColor: "{colors.on-primary}"',
74
+ ' typography: "{typography.label}"',
75
+ ' rounded: "{rounded.md}"',
76
+ " padding: 12px",
77
+ " primary-action-hover:",
78
+ ' backgroundColor: "{colors.primary-hover}"',
79
+ ' textColor: "{colors.on-primary}"',
80
+ ' typography: "{typography.label}"',
81
+ ' rounded: "{rounded.md}"',
82
+ " padding: 12px",
83
+ "---",
84
+ "",
85
+ "# Design System",
86
+ "",
87
+ "## Overview",
88
+ "",
89
+ "- This is a starter visual system for projects that have not defined their own design rules yet.",
90
+ "- User-authored tokens, brand rules and later product decisions take precedence over this starter baseline.",
91
+ "- Keep durable color, typography, spacing, radius, component and interaction choices here so future UI work does not drift.",
92
+ "",
93
+ "## Colors",
94
+ "",
95
+ "- Use `canvas` for the page background, `surface` for panels and `surface-muted` for quiet controls or secondary regions.",
96
+ "- Keep primary actions on `primary` with `on-primary` text; use `primary-hover` for hover and active emphasis.",
97
+ "- Avoid introducing decorative gradients, random accent colors or single-hue palettes unless the product brand explicitly calls for them.",
98
+ "",
99
+ "## Typography",
100
+ "",
101
+ "- Use `display` only for true page-level emphasis, `title` for section and panel headings, `body` for readable content and `label` for controls.",
102
+ "- Preserve hierarchy through size, weight and spacing before adding extra colors or decoration.",
103
+ "",
104
+ "## Layout",
105
+ "",
106
+ "- Start with clear page structure: canvas, surfaces, primary action, secondary controls and readable content regions.",
107
+ "- Use the spacing scale consistently; dense operational screens can reduce vertical whitespace, while marketing or editorial surfaces can breathe more.",
108
+ "- On small screens, stack content in priority order and keep primary actions reachable without overlapping other UI.",
109
+ "",
110
+ "## Components",
111
+ "",
112
+ "- Buttons, inputs, cards, navigation and dialogs need default, hover, active, focus, disabled, loading and error states when they are user-facing.",
113
+ "- Use `surface-card` for contained groups, `quiet-control` for low-emphasis controls and `primary-action` for the main command.",
114
+ "- Prefer clear affordances, visible focus states and stable dimensions so labels, icons and dynamic text do not resize the layout.",
115
+ "",
116
+ "## Do's and Don'ts",
117
+ "",
118
+ "- Do use this file as the first design fact source before generating mockups, Figma screens or frontend UI.",
119
+ "- Do replace or extend this starter when the project has a real brand, product category or design system.",
120
+ "- Do keep accessibility, responsive behavior and interaction states explicit.",
121
+ "- Don't treat this starter as a user brand decision once project-specific rules exist.",
122
+ "- Don't add generic AI-looking hero gradients, oversized cards or decorative blobs unless the product direction asks for them.",
123
+ "",
124
+ "## Design Change Workflow",
125
+ "",
126
+ "- Read this file before creating design drafts, redesigning UI, changing visual systems or polishing frontend styling.",
127
+ "- When there is a scan target such as UI source, page files, build output or a local/remote URL, run `npx impeccable detect <target>` before finalizing design changes.",
128
+ "- Treat Impeccable findings as design-review signals: fix issues that affect clarity, consistency, accessibility or trust, and note when there is no suitable scan target.",
129
+ "- After design decisions stabilize, update this file with durable tokens, component rules and do/don't guidance so later UI work stays aligned.",
130
+ ""
131
+ ].join("\n");
132
+ }
@@ -0,0 +1,6 @@
1
+ export interface DoctorReport {
2
+ info: string[];
3
+ warnings: string[];
4
+ errors: string[];
5
+ }
6
+ export declare function runDoctor(projectRoot: string): Promise<DoctorReport>;
@@ -0,0 +1,41 @@
1
+ import { createRequire } from "node:module";
2
+ import path from "node:path";
3
+ import { readConfig } from "./config.js";
4
+ import { harnessConfigPath, harnessRoot } from "./harness-root.js";
5
+ import { pathExists } from "./fs.js";
6
+ import { unsupportedSchemaMessage } from "./schema-guard.js";
7
+ const require = createRequire(import.meta.url);
8
+ const packageMetadata = require("../../package.json");
9
+ export async function runDoctor(projectRoot) {
10
+ const report = { info: [], warnings: [], errors: [] };
11
+ const root = await harnessRoot(projectRoot);
12
+ const relativeConfigPath = await harnessConfigPath(projectRoot);
13
+ const configPath = path.join(projectRoot, relativeConfigPath);
14
+ if (!(await pathExists(configPath))) {
15
+ report.errors.push(`missing ${relativeConfigPath}`);
16
+ return report;
17
+ }
18
+ const config = await readConfig(projectRoot);
19
+ const packageVersion = packageMetadata.version ?? "0.0.0";
20
+ report.info.push(`harness root: ${root}`);
21
+ report.info.push(`core package: ${config.core.package}@${packageVersion}`);
22
+ report.info.push(`schema version: ${config.core.schema_version}`);
23
+ const unsupportedSchema = unsupportedSchemaMessage(config.core.schema_version, "doctor");
24
+ if (unsupportedSchema) {
25
+ report.errors.push(unsupportedSchema);
26
+ return report;
27
+ }
28
+ for (const required of ["project_context/context.toml", "project_context/global.md", "project_context/architecture.md", "project_context/areas"]) {
29
+ if (!(await pathExists(path.join(projectRoot, required)))) {
30
+ report.errors.push(`missing ${required}`);
31
+ }
32
+ }
33
+ for (const managed of config.managed_files) {
34
+ const exists = await pathExists(path.join(projectRoot, managed.path));
35
+ if (!exists && managed.strategy !== "create-if-missing") {
36
+ report.warnings.push(`managed path missing: ${managed.path}`);
37
+ }
38
+ }
39
+ report.info.push("doctor complete");
40
+ return report;
41
+ }
@@ -0,0 +1,8 @@
1
+ export declare function pathExists(target: string): Promise<boolean>;
2
+ export declare function readText(target: string): Promise<string>;
3
+ export declare function ensureDir(target: string): Promise<void>;
4
+ export declare function writeTextIfChanged(target: string, content: string): Promise<boolean>;
5
+ export declare function listFiles(root: string): Promise<string[]>;
6
+ export declare function copyTree(sourceRoot: string, destinationRoot: string, options?: {
7
+ skipGitkeep?: boolean;
8
+ }): Promise<string[]>;
package/dist/lib/fs.js ADDED
@@ -0,0 +1,56 @@
1
+ import { promises as fs } from "node:fs";
2
+ import path from "node:path";
3
+ export async function pathExists(target) {
4
+ try {
5
+ await fs.access(target);
6
+ return true;
7
+ }
8
+ catch {
9
+ return false;
10
+ }
11
+ }
12
+ export async function readText(target) {
13
+ return fs.readFile(target, "utf8");
14
+ }
15
+ export async function ensureDir(target) {
16
+ await fs.mkdir(target, { recursive: true });
17
+ }
18
+ export async function writeTextIfChanged(target, content) {
19
+ if ((await pathExists(target)) && (await readText(target)) === content) {
20
+ return false;
21
+ }
22
+ await ensureDir(path.dirname(target));
23
+ await fs.writeFile(target, content, "utf8");
24
+ return true;
25
+ }
26
+ export async function listFiles(root) {
27
+ if (!(await pathExists(root))) {
28
+ return [];
29
+ }
30
+ const entries = await fs.readdir(root, { withFileTypes: true });
31
+ const files = [];
32
+ for (const entry of entries) {
33
+ const fullPath = path.join(root, entry.name);
34
+ if (entry.isDirectory()) {
35
+ files.push(...(await listFiles(fullPath)));
36
+ }
37
+ else if (entry.isFile()) {
38
+ files.push(fullPath);
39
+ }
40
+ }
41
+ return files;
42
+ }
43
+ export async function copyTree(sourceRoot, destinationRoot, options = {}) {
44
+ const changed = [];
45
+ for (const source of await listFiles(sourceRoot)) {
46
+ if (options.skipGitkeep && path.basename(source) === ".gitkeep") {
47
+ continue;
48
+ }
49
+ const relative = path.relative(sourceRoot, source);
50
+ const destination = path.join(destinationRoot, relative);
51
+ if (await writeTextIfChanged(destination, await readText(source))) {
52
+ changed.push(destination);
53
+ }
54
+ }
55
+ return changed;
56
+ }
@@ -0,0 +1,9 @@
1
+ export interface HarnessRootConfig {
2
+ harnessFolderName: string;
3
+ source: string;
4
+ }
5
+ export declare function readHarnessRootConfig(projectRoot: string): Promise<HarnessRootConfig>;
6
+ export declare function harnessRoot(projectRoot: string): Promise<string>;
7
+ export declare function harnessConfigPath(projectRoot: string): Promise<string>;
8
+ export declare function harnessPath(root: string, ...segments: string[]): string;
9
+ export declare function normalizeHarnessFolderName(value: string): string;
@@ -0,0 +1,50 @@
1
+ import path from "node:path";
2
+ import { DEFAULT_HARNESS_ROOT, HARNESS_JSON_CONFIG_PATH } from "./paths.js";
3
+ import { pathExists, readText } from "./fs.js";
4
+ export async function readHarnessRootConfig(projectRoot) {
5
+ const packageJson = await readJsonConfig(path.join(projectRoot, "package.json"));
6
+ const packageConfig = packageJson && typeof packageJson === "object" ? packageJson.sdlcHarness : undefined;
7
+ const packageValue = folderNameFromObject(packageConfig);
8
+ if (packageValue) {
9
+ return { harnessFolderName: normalizeHarnessFolderName(packageValue), source: "package.json#sdlcHarness" };
10
+ }
11
+ const explicitConfig = await readJsonConfig(path.join(projectRoot, HARNESS_JSON_CONFIG_PATH));
12
+ const explicitValue = folderNameFromObject(explicitConfig);
13
+ if (explicitValue) {
14
+ return { harnessFolderName: normalizeHarnessFolderName(explicitValue), source: HARNESS_JSON_CONFIG_PATH };
15
+ }
16
+ return { harnessFolderName: DEFAULT_HARNESS_ROOT, source: "default" };
17
+ }
18
+ export async function harnessRoot(projectRoot) {
19
+ return (await readHarnessRootConfig(projectRoot)).harnessFolderName;
20
+ }
21
+ export async function harnessConfigPath(projectRoot) {
22
+ return harnessPath(await harnessRoot(projectRoot), "config.yaml");
23
+ }
24
+ export function harnessPath(root, ...segments) {
25
+ return [root, ...segments].join("/").replace(/\/+/g, "/");
26
+ }
27
+ function folderNameFromObject(value) {
28
+ if (!value || typeof value !== "object") {
29
+ return undefined;
30
+ }
31
+ const record = value;
32
+ const folderName = record.harnessFolderName ?? record.harnessFloderName;
33
+ return typeof folderName === "string" && folderName.trim() ? folderName : undefined;
34
+ }
35
+ async function readJsonConfig(filePath) {
36
+ if (!(await pathExists(filePath))) {
37
+ return undefined;
38
+ }
39
+ return JSON.parse(await readText(filePath));
40
+ }
41
+ export function normalizeHarnessFolderName(value) {
42
+ const normalized = value.trim().replace(/\\/g, "/").replace(/\/+$/, "");
43
+ if (!normalized || normalized === "." || normalized === "..") {
44
+ throw new Error("harnessFolderName must be a non-empty relative directory");
45
+ }
46
+ if (path.isAbsolute(normalized) || normalized.includes("..")) {
47
+ throw new Error("harnessFolderName must not be absolute or contain '..'");
48
+ }
49
+ return normalized;
50
+ }
@@ -0,0 +1,5 @@
1
+ export interface InitOptions {
2
+ adopt: boolean;
3
+ force: boolean;
4
+ }
5
+ export declare function runInit(projectRoot: string, options: InitOptions): Promise<string[]>;