project-tiny-context-harness 0.2.51 → 0.2.53
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/README.md +23 -13
- package/assets/README.md +18 -14
- package/assets/agents/AGENTS_CORE.md +5 -5
- package/assets/make/sdlc-harness.mk +10 -6
- package/assets/skills/context_development_engineer/SKILL.md +7 -0
- package/assets/skills/context_harness_upgrade/SKILL.md +57 -0
- package/dist/commands/check-modularity.d.ts +1 -0
- package/dist/commands/check-modularity.js +137 -0
- package/dist/commands/index.js +6 -2
- package/dist/commands/upgrade.js +10 -4
- package/dist/lib/context-export.js +1 -137
- package/dist/lib/migrations.d.ts +1 -1
- package/dist/lib/migrations.js +23 -5
- package/dist/lib/modularity.d.ts +18 -0
- package/dist/lib/modularity.js +109 -0
- package/dist/lib/source-files.d.ts +4 -0
- package/dist/lib/source-files.js +138 -0
- package/dist/lib/upgrade.d.ts +9 -0
- package/dist/lib/upgrade.js +27 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -115,7 +115,7 @@ npm ci
|
|
|
115
115
|
npm run smoke:quickstart
|
|
116
116
|
npm run preview:pack
|
|
117
117
|
cd /path/to/your/test-repo
|
|
118
|
-
npm install -D /path/to/project-tiny-context-harness/tmp/sdlc/source-preview/package/project-tiny-context-harness-0.2.
|
|
118
|
+
npm install -D /path/to/project-tiny-context-harness/tmp/sdlc/source-preview/package/project-tiny-context-harness-0.2.53.tgz
|
|
119
119
|
npx --no-install sdlc-harness init --adopt
|
|
120
120
|
make validate-context
|
|
121
121
|
```
|
|
@@ -184,7 +184,7 @@ For existing projects:
|
|
|
184
184
|
npx --yes --package project-tiny-context-harness@latest sdlc-harness init --adopt
|
|
185
185
|
```
|
|
186
186
|
|
|
187
|
-
`init` creates `project_context/context.toml`, `project_context/global.md`, `project_context/architecture.md`, `project_context/areas/main.md`, `project_context/areas/main/verification.md`, agent guidance, Context authoring Skills, a full-project export Skill, managed templates/tools, a Makefile include and `.github/workflows/harness.yml`. The generated workflow runs only the selected Harness gate, `validate-context` or `validate-harness`; maintainer-only package tests and source-drift checks are intentionally kept out of consumer projects. It does not create stage work-product trees, lifecycle state or stage skills by default.
|
|
187
|
+
`init` creates `project_context/context.toml`, `project_context/global.md`, `project_context/architecture.md`, `project_context/areas/main.md`, `project_context/areas/main/verification.md`, agent guidance, Context authoring Skills, a full-project export Skill, a Harness upgrade Skill, managed templates/tools, a Makefile include and `.github/workflows/harness.yml`. The generated workflow runs only the selected Harness gate, `validate-context` or `validate-harness`; maintainer-only package tests and source-drift checks are intentionally kept out of consumer projects. It does not create stage work-product trees, lifecycle state or stage skills by default.
|
|
188
188
|
|
|
189
189
|
## FAQ
|
|
190
190
|
|
|
@@ -200,7 +200,7 @@ The support assets can live in a tool-specific harness folder such as `.codex`,
|
|
|
200
200
|
|
|
201
201
|
**Is this an English-only or Chinese-only tool?**
|
|
202
202
|
|
|
203
|
-
Neither. Public docs, npm copy
|
|
203
|
+
Neither. Public docs, npm copy, launch posts, CLI help/errors, generated Skill activation and default artifact names must be fully usable in English. Generated Skills may include multilingual trigger examples, but those examples are additive compatibility; every supported non-English trigger needs an equivalent narrow English trigger.
|
|
204
204
|
|
|
205
205
|
**Does `validate-context` prove the project works?**
|
|
206
206
|
|
|
@@ -227,20 +227,26 @@ Use `npx --no-install sdlc-harness ...` only when you explicitly want the alread
|
|
|
227
227
|
| UI/UX design Skill | `<harnessRoot>/skills/context_uiux_design/SKILL.md` | Handles explicit UI/UX design requests, writes screen/interaction conclusions to `project_context/**`, updates root `DESIGN.md` visual tokens with Google `@google/design.md`, and includes compact visual-quality calibration for product/page positioning, user needs, information density, brand/product UI and common AI-design anti-patterns. |
|
|
228
228
|
| Development engineer Skill | `<harnessRoot>/skills/context_development_engineer/SKILL.md` | Handles explicit development-engineering requests and writes durable engineering conclusions to `project_context/**`. |
|
|
229
229
|
| Full project context export Skill | `<harnessRoot>/skills/context_full_project_export/SKILL.md` | Handles explicit full-project or code-level export requests and uses `export-context --all`, `--full` or `--code` to create temporary artifacts under `tmp/sdlc/context-exports/**`. |
|
|
230
|
-
|
|
|
230
|
+
| Harness upgrade Skill | `<harnessRoot>/skills/context_harness_upgrade/SKILL.md` | Handles explicit Tiny Context / Project Tiny Context Harness upgrade requests such as “upgrade Tiny Context” and “use the Tiny Context upgrade skill to upgrade this project”; it runs `upgrade` first, handles only migration-scoped `manual_required` / `blocked` follow-up, then runs diagnostics. |
|
|
231
|
+
| Project-local Skills | `<harnessRoot>/skills/<role>/SKILL.md` | Optional local product/design/development Skills created by the project, such as `product_plan`, `uiux_design` or `development_engineer`. They supersede package-managed default Skills when more specific, are not overwritten by `sync`, and should keep front matter trigger keywords aligned with the project `AGENTS.md` role-trigger rule. |
|
|
231
232
|
| Managed file sync | `make sdlc-sync` or `npx --yes --package project-tiny-context-harness@latest sdlc-harness sync` | Refreshes package-managed guidance, default Skills, Makefile include, context templates, tools and workflow YAML. It does not run migrations or perform semantic Context generation; when migration work is pending it refuses to write and tells you to run `upgrade`. |
|
|
232
|
-
| Upgrade | `make sdlc-upgrade` or `npx --yes --package project-tiny-context-harness@latest sdlc-harness upgrade` | Default command after updating the npm package. Builds an upgrade plan, applies `safe_pending` migrations, runs `sync` and `doctor`, and exits non-zero when manual
|
|
233
|
+
| Upgrade | `make sdlc-upgrade` or `npx --yes --package project-tiny-context-harness@latest sdlc-harness upgrade` | Default command after updating the npm package. Builds an upgrade plan, stops before writes when `blocked` items exist, otherwise applies `safe_pending` migrations, runs `sync` and `doctor`, and exits non-zero when manual follow-up or diagnostics remain. |
|
|
233
234
|
| Upgrade check | `npx --yes --package project-tiny-context-harness@latest sdlc-harness upgrade --check [--json]` | Checks the upgrade plan without writing files. Reports `safe_pending`, `manual_required` and `blocked`; exits non-zero when any work remains. |
|
|
234
235
|
| Combined project export | `npx --yes --package project-tiny-context-harness@latest sdlc-harness export-context --all [--check]` | Creates both default temporary exports under `tmp/sdlc/context-exports/**`. |
|
|
235
236
|
| Project Context export | `npx --yes --package project-tiny-context-harness@latest sdlc-harness export-context --full [--output tmp/sdlc/context-exports/name.md] [--check]` | Creates a temporary Context summary artifact. It is not Context and must not be registered in `project_context/context.toml`. |
|
|
236
237
|
| Code implementation export | `npx --yes --package project-tiny-context-harness@latest sdlc-harness export-context --code [--output tmp/sdlc/context-exports/name.md] [--check]` | Creates a temporary single-file code implementation artifact. It is not Context and must not be registered in `project_context/context.toml`. |
|
|
237
|
-
|
|
|
238
|
+
| Modularity check | `npx --yes --package project-tiny-context-harness@latest sdlc-harness check-modularity --touched [--limit 300] [--fail-on-warning]` | Warns when selected handwritten source files exceed a physical line-count limit; `--file <path>` and `--base <ref>` select explicit files or branch changes. |
|
|
239
|
+
| Context validation | `npx --yes --package project-tiny-context-harness@latest sdlc-harness validate-context`, `make validate-context` | Checks required project recovery fields, Context graph metadata, declared paths/roles and fake test-execution claims. |
|
|
238
240
|
| Diagnostics | `make sdlc-doctor` or `npx --yes --package project-tiny-context-harness@latest sdlc-harness doctor` | Reports Harness root, package version, schema version and required Minimal Context paths. |
|
|
239
241
|
| Package source checks | `sdlc-harness package sync-source`, `sdlc-harness package check-source` | Maintainer-only commands for keeping package canonical assets aligned with the source workspace. |
|
|
240
242
|
|
|
241
|
-
For product, UI/UX and engineering tasks that touch design intent, API/Schema, state/runtime behavior, architecture boundaries or verification design, the default Skills compile a short current-task contract before implementation. The contract starts with `Context Delta: none|required`; `required` preserves context-first behavior, while `none` means the task can proceed against existing Context. For module design work, the development engineer Skill also compiles `Applicable Module Design`: the relevant principles, minimal design logic and durable rationale that control the current implementation or verification choice. The task contract and Contract Conformance are handoff evidence, not new PRD, tech-plan, validator or gate surfaces.
|
|
243
|
+
For product, UI/UX and engineering tasks that touch design intent, API/Schema, state/runtime behavior, architecture boundaries or verification design, the default Skills compile a short current-task contract before implementation. The contract starts with `Context Delta: none|required`; `required` preserves context-first behavior, while `none` means the task can proceed against existing Context. For engineering, RFC and implementation work, the existing Task Contract also includes `Modularity Check: none|required|exception` so oversized touched files trigger split-or-exception reasoning. For module design work, the development engineer Skill also compiles `Applicable Module Design`: the relevant principles, minimal design logic and durable rationale that control the current implementation or verification choice. The task contract and Contract Conformance are handoff evidence, not new PRD, tech-plan, validator or gate surfaces.
|
|
242
244
|
|
|
243
|
-
|
|
245
|
+
`sdlc-harness check-modularity` supports that field by auditing selected handwritten source files for physical line-count risk. It is warning-only by default and is not `validate-context`; teams that want CI enforcement can opt in with `--fail-on-warning`. The command provides file/line facts, while the agent still decides `none`, `required` or `exception` through the development engineer decomposition guidance.
|
|
246
|
+
|
|
247
|
+
Multilingual trigger phrases are compatibility details. Public README, npm and launch copy stay English-first, and public/package-managed surfaces must remain English-complete; literal non-English examples are documented only where they explain generated Skill matching and must not be the sole activation path.
|
|
248
|
+
|
|
249
|
+
The Harness upgrade Skill exists so consumer agents have a short, repeatable upgrade procedure for existing projects. It treats `sdlc-harness upgrade` as the default command after package updates, forbids standalone `sync` before upgrade, and limits manual handling to the migration scope reported by the CLI instead of guessing project semantics.
|
|
244
250
|
|
|
245
251
|
For complex task-contract work, agents may use `plan.md` or an equivalent temporary plan surface as scratch space for `Context Delta`, `Task Contract`, implementation steps and Conformance notes. It is execution cache only: durable facts must be extracted into `project_context/**` or `DESIGN.md`, and temporary plans are not Context, not registered in `context.toml` and not default project assets.
|
|
246
252
|
|
|
@@ -369,7 +375,9 @@ mkdir -p <harnessRoot>/skills/uiux_design
|
|
|
369
375
|
$EDITOR <harnessRoot>/skills/uiux_design/SKILL.md
|
|
370
376
|
```
|
|
371
377
|
|
|
372
|
-
When a project-local Skill and a package-managed default Skill both apply, agents should use the more specific project-local Skill first. The local Skill should keep durable conclusions in `project_context/**` and `DESIGN.md`. Its front matter `description` should stay aligned with the matching default `context_*` Skill and the project `AGENTS.md` role-trigger rule; update both the local Skill and agent guidance when adding or narrowing product/design/development trigger terms. `sync` does not merge Skill overrides and does not overwrite separate project-local Skills. Existing `<harnessRoot>/pjsdlc_managed/override_skills/*.md` files should be migrated into standalone project-local Skills before running `sync`.
|
|
378
|
+
When a project-local Skill and a package-managed default Skill both apply, agents should use the more specific project-local Skill first. The local Skill should keep durable conclusions in `project_context/**` and `DESIGN.md`. Its front matter `description` should stay aligned with the matching default `context_*` Skill and the project `AGENTS.md` role-trigger rule; update both the local Skill and agent guidance when adding or narrowing product/design/development trigger terms. `sync` does not merge Skill overrides and does not overwrite separate project-local Skills. Existing `<harnessRoot>/pjsdlc_managed/override_skills/*.md` files should be migrated into standalone project-local Skills before running `sync`.
|
|
379
|
+
|
|
380
|
+
Do not customize the package-managed Harness upgrade Skill directly. Project-specific upgrade facts belong in `project_context/**`; recurring project-local procedures belong in separate project-local Skills.
|
|
373
381
|
|
|
374
382
|
## Sync And Upgrade Boundary
|
|
375
383
|
|
|
@@ -377,7 +385,7 @@ When a project-local Skill and a package-managed default Skill both apply, agent
|
|
|
377
385
|
|
|
378
386
|
After updating the package, run `sdlc-harness upgrade`. Use `sync` only when release notes say the update is `sync-only`.
|
|
379
387
|
|
|
380
|
-
`upgrade` first builds an upgrade plan, applies only `safe_pending` migrations, then runs `sync` and `doctor`. If `manual_required` or
|
|
388
|
+
`upgrade` first builds an upgrade plan. If `blocked` items exist, it prints the plan, runs diagnostics and exits non-zero before migrations or internal `sync`. Without blockers, it applies only `safe_pending` migrations, then runs `sync` and `doctor`. If `manual_required` follow-up or diagnostics remain, the command exits non-zero and prints follow-up. `upgrade --check` performs the same planning step without writing files; `upgrade --check --json` is intended for release checks and CI.
|
|
381
389
|
|
|
382
390
|
Release update modes:
|
|
383
391
|
|
|
@@ -393,7 +401,7 @@ Migration statuses:
|
|
|
393
401
|
|---|---|
|
|
394
402
|
| `safe_pending` | A known Harness schema, config or path convention can be migrated mechanically. |
|
|
395
403
|
| `manual_required` | The path is in migration scope, but the Harness cannot prove the right semantic role or user intent. |
|
|
396
|
-
| `blocked` | A target conflict or overwrite risk prevents a safe write. |
|
|
404
|
+
| `blocked` | A target conflict or overwrite risk prevents a safe write. Blocked items stop upgrade writes until resolved. |
|
|
397
405
|
|
|
398
406
|
Examples:
|
|
399
407
|
|
|
@@ -401,7 +409,7 @@ Examples:
|
|
|
401
409
|
- Missing `project_context/context.toml` can receive a conservative baseline manifest.
|
|
402
410
|
- `project_context/areas/main/verification.md` can be registered as `verification` by path convention.
|
|
403
411
|
- `project_context/areas/payment/api.md` without a manifest role is `manual_required`; the Harness does not guess whether it is an area, contract, foundation or implementation index.
|
|
404
|
-
- If the target already exists, the migration is `blocked` and no file is overwritten.
|
|
412
|
+
- If the target already exists, the migration is `blocked`; `upgrade` stops before migrations or `sync`, and no file is overwritten.
|
|
405
413
|
|
|
406
414
|
The former migration command has been removed because existing users have completed that migration path.
|
|
407
415
|
|
|
@@ -410,9 +418,11 @@ The former migration command has been removed because existing users have comple
|
|
|
410
418
|
```sh
|
|
411
419
|
npx --yes --package project-tiny-context-harness@latest sdlc-harness init
|
|
412
420
|
npx --yes --package project-tiny-context-harness@latest sdlc-harness init --adopt
|
|
413
|
-
npx --yes --package project-tiny-context-harness@latest sdlc-harness export-context --all
|
|
421
|
+
npx --yes --package project-tiny-context-harness@latest sdlc-harness export-context --all
|
|
414
422
|
npx --yes --package project-tiny-context-harness@latest sdlc-harness export-context --full
|
|
415
423
|
npx --yes --package project-tiny-context-harness@latest sdlc-harness export-context --code
|
|
424
|
+
make sdlc-check-modularity
|
|
425
|
+
npx --yes --package project-tiny-context-harness@latest sdlc-harness check-modularity --touched
|
|
416
426
|
make sdlc-sync
|
|
417
427
|
make sdlc-upgrade
|
|
418
428
|
npx --yes --package project-tiny-context-harness@latest sdlc-harness upgrade --check
|
package/assets/README.md
CHANGED
|
@@ -94,7 +94,7 @@ That smoke packs the local workspace, installs it into a disposable repo, runs `
|
|
|
94
94
|
```sh
|
|
95
95
|
npm run preview:pack
|
|
96
96
|
cd /path/to/your/test-repo
|
|
97
|
-
npm install -D /path/to/project-tiny-context-harness/tmp/sdlc/source-preview/package/project-tiny-context-harness-0.2.
|
|
97
|
+
npm install -D /path/to/project-tiny-context-harness/tmp/sdlc/source-preview/package/project-tiny-context-harness-0.2.53.tgz
|
|
98
98
|
npx --no-install sdlc-harness init --adopt
|
|
99
99
|
make validate-context
|
|
100
100
|
```
|
|
@@ -225,10 +225,11 @@ npx --yes --package project-tiny-context-harness@latest sdlc-harness init --adop
|
|
|
225
225
|
- `project_context/areas/main.md`
|
|
226
226
|
- `project_context/areas/main/verification.md`
|
|
227
227
|
- `<harnessRoot>/config.yaml`
|
|
228
|
-
- `<harnessRoot>/skills/context_product_plan/SKILL.md`
|
|
229
|
-
- `<harnessRoot>/skills/context_uiux_design/SKILL.md`
|
|
230
|
-
- `<harnessRoot>/skills/context_development_engineer/SKILL.md`
|
|
231
|
-
- `<harnessRoot>/skills/context_full_project_export/SKILL.md`
|
|
228
|
+
- `<harnessRoot>/skills/context_product_plan/SKILL.md`
|
|
229
|
+
- `<harnessRoot>/skills/context_uiux_design/SKILL.md`
|
|
230
|
+
- `<harnessRoot>/skills/context_development_engineer/SKILL.md`
|
|
231
|
+
- `<harnessRoot>/skills/context_full_project_export/SKILL.md`
|
|
232
|
+
- `<harnessRoot>/skills/context_harness_upgrade/SKILL.md`
|
|
232
233
|
- `<harnessRoot>/pjsdlc_managed/context_templates/**`
|
|
233
234
|
- `<harnessRoot>/pjsdlc_managed/make/sdlc-harness.mk`
|
|
234
235
|
- `tools/**`
|
|
@@ -253,7 +254,7 @@ The support assets can live in a tool-specific harness folder such as `.codex`,
|
|
|
253
254
|
|
|
254
255
|
**Is this an English-only or Chinese-only tool?**
|
|
255
256
|
|
|
256
|
-
Neither. Public docs, npm copy
|
|
257
|
+
Neither. Public docs, npm copy, launch posts, CLI help/errors, generated Skill activation and default artifact names must be fully usable in English. Generated Skills may include multilingual trigger examples, but those examples are additive compatibility; every supported non-English trigger needs an equivalent narrow English trigger.
|
|
257
258
|
|
|
258
259
|
**Does `validate-context` prove the project works?**
|
|
259
260
|
|
|
@@ -263,11 +264,13 @@ No. It checks that recovery facts exist and avoids fake test-result claims. Prod
|
|
|
263
264
|
|
|
264
265
|
It should stay smaller than a full process. Ordinary bug fixes and local refactors do not update Context unless they produce durable product, architecture, API, state or validation facts.
|
|
265
266
|
|
|
266
|
-
The default Skills are Minimal Context helpers for explicit product-planning, UI/UX-design, development-engineering
|
|
267
|
+
The default Skills are Minimal Context helpers for explicit product-planning, UI/UX-design, development-engineering, full-project-export and Tiny Context upgrade requests. Product, screen-flow and durable engineering conclusions go to `project_context/**`; visual identity and design tokens go to root `DESIGN.md`. Export artifacts are temporary files under `tmp/sdlc/context-exports/**`, not Context. The Harness upgrade Skill handles requests such as “upgrade Tiny Context” and “use the Tiny Context upgrade skill to upgrade this project” by running `upgrade` first, handling only migration-scoped follow-up, and avoiding standalone `sync` before the upgrade path.
|
|
267
268
|
|
|
268
|
-
Multilingual trigger phrases
|
|
269
|
+
Multilingual trigger phrases are compatibility details. Public README, npm and launch copy stay English-first, and public/package-managed surfaces must remain English-complete; literal non-English examples are documented only where they explain generated Skill matching and must not be the sole activation path.
|
|
269
270
|
|
|
270
|
-
For product, UI/UX and engineering tasks that touch design intent, API/Schema, state/runtime behavior, architecture boundaries or verification design, the default Skills compile a short current-task contract before implementation. The contract starts with `Context Delta: none|required`; `required` preserves context-first behavior, while `none` means the task can proceed against existing Context. For module design work, the development engineer Skill also compiles `Applicable Module Design`: the relevant principles, minimal design logic and durable rationale that control the current implementation or verification choice. The task contract and Contract Conformance are handoff evidence, not new PRD, tech-plan, validator or gate surfaces.
|
|
271
|
+
For product, UI/UX and engineering tasks that touch design intent, API/Schema, state/runtime behavior, architecture boundaries or verification design, the default Skills compile a short current-task contract before implementation. The contract starts with `Context Delta: none|required`; `required` preserves context-first behavior, while `none` means the task can proceed against existing Context. For engineering, RFC and implementation work, the existing Task Contract also includes `Modularity Check: none|required|exception` so oversized touched files trigger split-or-exception reasoning. For module design work, the development engineer Skill also compiles `Applicable Module Design`: the relevant principles, minimal design logic and durable rationale that control the current implementation or verification choice. The task contract and Contract Conformance are handoff evidence, not new PRD, tech-plan, validator or gate surfaces.
|
|
272
|
+
|
|
273
|
+
`sdlc-harness check-modularity` supports that field by auditing selected handwritten source files for physical line-count risk. It is warning-only by default and is not `validate-context`; teams that want CI enforcement can opt in with `--fail-on-warning`. The command provides file/line facts, while the agent still decides `none`, `required` or `exception` through the development engineer decomposition guidance.
|
|
271
274
|
|
|
272
275
|
For complex task-contract work, agents may use `plan.md` or an equivalent temporary plan surface as scratch space for `Context Delta`, `Task Contract`, implementation steps and Conformance notes. It is execution cache only: durable facts must be extracted into `project_context/**` or `DESIGN.md`, and temporary plans are not Context, not registered in `context.toml` and not default project assets.
|
|
273
276
|
|
|
@@ -277,7 +280,7 @@ The expected Context Priority Ladder is: read Context first, run the page produc
|
|
|
277
280
|
|
|
278
281
|
Managed `AGENTS.md` guidance is intentionally a startup router, not a full manual. It should contain fact-source entry points, hard boundaries, key triggers and shortest validation commands; package consumers default long design reasoning to Context unless they already have a local spec/design convention. In this source workspace, `PROJECT_SPEC.md` holds stable Harness workflow rationale. Role procedures belong in Skills and human usage guidance in README. The recommended 40-70 line range is a soft budget, not a validator gate.
|
|
279
282
|
|
|
280
|
-
The default `context_*` Skills are package-managed generated files. `sync` overwrites them, so project-specific product/design/development rules should live in separate project-local Skills such as `.codex/skills/product_plan/SKILL.md`, `.codex/skills/uiux_design/SKILL.md` or `.codex/skills/development_engineer/SKILL.md`. When a project-local Skill and a default Skill both apply, agents should use the more specific project-local Skill first while keeping durable conclusions in `project_context/**` and `DESIGN.md`. Keep the project-local Skill front matter `description` triggers aligned with the matching default `context_*` Skill and the project `AGENTS.md` role-trigger rule; if a project adds or narrows product/design/development keywords, update both the local Skill and the agent guidance together.
|
|
283
|
+
The default `context_*` Skills are package-managed generated files. `sync` overwrites them, so project-specific product/design/development rules should live in separate project-local Skills such as `.codex/skills/product_plan/SKILL.md`, `.codex/skills/uiux_design/SKILL.md` or `.codex/skills/development_engineer/SKILL.md`. When a project-local Skill and a default Skill both apply, agents should use the more specific project-local Skill first while keeping durable conclusions in `project_context/**` and `DESIGN.md`. Keep the project-local Skill front matter `description` triggers aligned with the matching default `context_*` Skill and the project `AGENTS.md` role-trigger rule; if a project adds or narrows product/design/development keywords, update both the local Skill and the agent guidance together. The Harness upgrade Skill is also package-managed; customize project semantics in Context, not by editing that generated Skill.
|
|
281
284
|
|
|
282
285
|
## CLI Entry Safety
|
|
283
286
|
|
|
@@ -291,11 +294,12 @@ Use `npx --no-install sdlc-harness ...` only when you explicitly want the alread
|
|
|
291
294
|
|---|---|
|
|
292
295
|
| `npx --yes --package project-tiny-context-harness@latest sdlc-harness init` | Non-destructively installs Minimal Context Harness into the current project. |
|
|
293
296
|
| `make sdlc-sync` or `npx --yes --package project-tiny-context-harness@latest sdlc-harness sync` | Refreshes managed guidance, default Skills, Makefile include, tools and templates. It does not run migrations or generate project semantics; when migration work is pending it refuses to write and tells you to run `upgrade`. |
|
|
294
|
-
| `make sdlc-upgrade` or `npx --yes --package project-tiny-context-harness@latest sdlc-harness upgrade` | Default command after updating the npm package. Builds an upgrade plan, applies `safe_pending` migrations, runs `sync` and `doctor`, and exits non-zero when manual
|
|
297
|
+
| `make sdlc-upgrade` or `npx --yes --package project-tiny-context-harness@latest sdlc-harness upgrade` | Default command after updating the npm package. Builds an upgrade plan, stops before writes when `blocked` items exist, otherwise applies `safe_pending` migrations, runs `sync` and `doctor`, and exits non-zero when manual follow-up or diagnostics remain. |
|
|
295
298
|
| `npx --yes --package project-tiny-context-harness@latest sdlc-harness upgrade --check [--json]` | Checks the upgrade plan without writing files. Reports `safe_pending`, `manual_required` and `blocked`; exits non-zero when any work remains. |
|
|
296
299
|
| `npx --yes --package project-tiny-context-harness@latest sdlc-harness export-context --all [--check]` | Creates both default temporary exports under `tmp/sdlc/context-exports/**`. |
|
|
297
300
|
| `npx --yes --package project-tiny-context-harness@latest sdlc-harness export-context --full [--output tmp/sdlc/context-exports/name.md] [--check]` | Creates a temporary project Context summary Markdown artifact. |
|
|
298
301
|
| `npx --yes --package project-tiny-context-harness@latest sdlc-harness export-context --code [--output tmp/sdlc/context-exports/name.md] [--check]` | Creates a temporary single-file code implementation Markdown artifact. |
|
|
302
|
+
| `npx --yes --package project-tiny-context-harness@latest sdlc-harness check-modularity --touched [--limit 300] [--fail-on-warning]` | Warns when selected handwritten source files exceed a physical line-count limit; `--file <path>` and `--base <ref>` select explicit files or branch changes. |
|
|
299
303
|
| `npx --yes --package project-tiny-context-harness@latest sdlc-harness validate-context` | Checks minimum project recovery fields, Context graph metadata, declared paths/roles and fake test-execution claims. |
|
|
300
304
|
| `make validate-context` | Makefile wrapper for `validate-context`. |
|
|
301
305
|
| `make validate-harness` | Compatibility alias for `validate-context` in vNext projects. |
|
|
@@ -326,9 +330,9 @@ Release notes and release readiness use this update mode vocabulary:
|
|
|
326
330
|
|---|---|
|
|
327
331
|
| `safe_pending` | The Harness can prove the change is inside a known Harness-owned schema, config or path convention and can apply it mechanically. |
|
|
328
332
|
| `manual_required` | The file is in migration scope, but the Harness cannot prove the right semantic role or user intent. It prints the path and follow-up. |
|
|
329
|
-
| `blocked` | A safe target cannot be written, usually because the destination already exists or another conflict would require overwriting user content. |
|
|
333
|
+
| `blocked` | A safe target cannot be written, usually because the destination already exists or another conflict would require overwriting user content. Blocked items stop upgrade writes until resolved. |
|
|
330
334
|
|
|
331
|
-
`upgrade` promises to refresh package-managed assets, apply known safe migrations, avoid overwriting user custom content, expose manual-required migration scope, and run `doctor` / `validate-context` style diagnostics so remaining problems are visible. It does not automatically understand the user's project semantics, decide every Context role, repair project-local Skills, invent business verification paths, update product/deployment facts or turn an old project into the current best-practice shape.
|
|
335
|
+
`upgrade` promises to refresh package-managed assets, apply known safe migrations when no blocked target conflict exists, avoid overwriting user custom content, expose manual-required migration scope, and run `doctor` / `validate-context` style diagnostics so remaining problems are visible. It does not automatically understand the user's project semantics, decide every Context role, repair project-local Skills, invent business verification paths, update product/deployment facts or turn an old project into the current best-practice shape.
|
|
332
336
|
|
|
333
337
|
Examples:
|
|
334
338
|
|
|
@@ -336,7 +340,7 @@ Examples:
|
|
|
336
340
|
- A missing `project_context/context.toml` can receive a conservative baseline manifest.
|
|
337
341
|
- `project_context/areas/main/verification.md` can be registered as a `verification` role by path convention.
|
|
338
342
|
- `project_context/areas/payment/api.md` without a manifest role is reported as `manual_required`; the Harness does not guess whether it is an area, contract, foundation or implementation index.
|
|
339
|
-
- If `project_context/areas/main.md` already exists while `project_context/modules/main.md` still exists, the migration is `blocked` and no file is overwritten.
|
|
343
|
+
- If `project_context/areas/main.md` already exists while `project_context/modules/main.md` still exists, the migration is `blocked`; `upgrade` stops before migrations or `sync`, and no file is overwritten.
|
|
340
344
|
|
|
341
345
|
## Minimal Context Files
|
|
342
346
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
1. 先读 `project_context/global.md`、`project_context/architecture.md` 和 `project_context/context.toml`,再按 graph 读取相关 area / context unit。
|
|
14
14
|
2. 若任务涉及 Web 页面、前端布局、UI/UX、产品模块边界或信息放置,先做页面产品定位检查,再完成变更分类;若 UI 改动涉及输入、选择、搜索、筛选、表单/配置、调度/时间窗口、预算/配额/限流或加载/空态/错误态,按产品/UIUX Skill 的控件任务框架做轻量检查。
|
|
15
15
|
3. 若任务新增、迁移或整理 Context 文件,先做 role placement scan:area 只代表产品域归属;contract / foundation / subdomain / verification / deployment / implementation-index / decision-rationale 等按读取目的拆成 role Context。
|
|
16
|
-
4. 对产品方案、UI/UX、系统设计、架构边界、API / Schema、模块设计原则、状态或运行语义、验证设计等任务,先把相关模块设计上下文编译进当前任务契约;契约第一段用 `Context Delta: none|required` 声明是否改变长期事实,再写本次 Task Contract
|
|
16
|
+
4. 对产品方案、UI/UX、系统设计、架构边界、API / Schema、模块设计原则、状态或运行语义、验证设计等任务,先把相关模块设计上下文编译进当前任务契约;契约第一段用 `Context Delta: none|required` 声明是否改变长期事实,再写本次 Task Contract;工程 / RFC / 实现类 Task Contract 同时包含 `Modularity Check: none|required|exception`。
|
|
17
17
|
5. `Context Delta: required` 则 context-first;普通 bug fix、局部样式、局部漂移修复、测试修复或 spike 默认 code-first,但一旦产生长期结论必须回写 Context。
|
|
18
18
|
6. 收尾做 Contract Conformance 和 Context drift check,只报告 `Context: 已更新 ...` 或 `Context: 本次无长期事实变化`;不要把一次性证据、任务契约或实现摘要写入 Context。
|
|
19
19
|
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
|
|
30
30
|
1. 新会话或继续工作时,先读取 `project_context/global.md`、`project_context/architecture.md` 和 `project_context/context.toml`;按其中 default area 和触发条件读取相关 context。
|
|
31
31
|
2. 第一处代码编辑前先做轻量变更分类,不按固定时长计时。若任务涉及 Web 页面、前端布局、UI/UX、产品模块边界或信息应该放在哪个页面 / 模块,先做页面产品定位检查,再完成变更分类:用户在这个页面要完成什么判断,产品必须提供哪些信息 / 动作 / 反馈,哪些信息不应常驻,哪些属于下游消费层 / 运维层 / 详情层 / 其他页面,当前布局和信息密度是否匹配页面任务。若 UI 改动涉及输入、选择、搜索、筛选、表单/配置、调度/时间窗口、预算/配额/限流或加载/空态/错误态,按产品/UIUX Skill 的控件任务框架做轻量检查,识别既有 Context 是否适用以及是否缺少长期页面/控件契约。多页面或多模块归属不清时,先审查全站或相关页面的信息架构,再收窄到代码模块实现。该检查是判断是否需要 context-first 的输入,不等于必须更新 Context,也不要求独立文档或新的 gate。
|
|
32
|
-
3. 对产品方案、UI/UX、系统设计、架构边界、API / Schema、模块设计原则、状态机或运行语义、验证设计等任务,第一处代码编辑前先编译当前任务契约:用 `Context Delta: none|required` 作为唯一正式长期事实判断点,再写本次 Task Contract,并把命中的模块设计上下文、它控制的当前选择、首选路径和 fallback / degraded path
|
|
32
|
+
3. 对产品方案、UI/UX、系统设计、架构边界、API / Schema、模块设计原则、状态机或运行语义、验证设计等任务,第一处代码编辑前先编译当前任务契约:用 `Context Delta: none|required` 作为唯一正式长期事实判断点,再写本次 Task Contract,并把命中的模块设计上下文、它控制的当前选择、首选路径和 fallback / degraded path 条件写入任务契约;工程 / RFC / 实现类 Task Contract 还应包含 `Modularity Check: none|required|exception`。普通 bug fix、局部样式、小重构、局部漂移修复、测试修复或 spike 不强制编译任务契约。
|
|
33
33
|
4. 当新增、迁移或整理 `project_context/areas/**` 时,做 role placement scan(软约束,不做 gate):`area` / `domain` 保留产品域归属,`subdomain` 用于产品域内较小 ownership,`contract` 用于 API / schema / event / 跨域接口语义,`foundation` 用于稳定理论 / 词汇 / 背景材料,`verification` / `deployment` 用于可复用执行路径,`implementation-index` 只做代码导航索引,`decision-rationale` 记录稳定设计原因,`archive` 用于非默认读取的历史或外部材料。
|
|
34
34
|
5. 若任务契约声明 `Context Delta: required`,默认走 context-first:第一处代码编辑前先更新相关 `project_context/**`,写入必要且足以指导实现的长期结论,再按 Context 和 Task Contract 对齐实现、验证和收尾。
|
|
35
35
|
6. 普通 bug fix、局部样式、局部实现漂移修复、测试修复或探索性 spike 不更新 Context,可先改代码;一旦形成长期结论,继续对齐或交付前必须回写 `project_context/**`。
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
8. 当代码形态、搜索结果或相邻实现与 Context 声明冲突时,把差异视为实现漂移、缺失工作或 Context 过期并显式说明;不要用当前代码形态或关键词搜索结果覆盖 Context 已声明的职责、归属或集成意图。
|
|
38
38
|
9. 每个有意义的方案或实现变更收尾时做 Contract Conformance 和 Context drift check:对照 Task Contract 区分实现偏差、契约遗漏或长期事实缺失;实现偏差修实现,契约遗漏回 Task Contract,长期事实缺失回 `Context Delta` 并先更新 Context。交付说明只报告轻量状态:`Context: 已更新 ...` 或 `Context: 本次无长期事实变化`;Conformance 证据属于本次交付,不写入 `project_context/**`。
|
|
39
39
|
10. 长期事实只写入 `project_context/**`;不要默认创建 PRD、tech plan、ADR、implementation doc、review/test/release 文档。
|
|
40
|
-
11. 用户明确要求“产品方案 / 产品经理 /
|
|
41
|
-
12. 用户明确要求“导出尽可能详细的项目全量上下文 / 全量上下文导出 / full project context export / 当前项目代码实现 /
|
|
40
|
+
11. 用户明确要求“产品方案 / 产品经理 / 产品专家 / product plan / product manager / product spec”、“设计稿 / UI/UX 设计方案 / 视觉专家 / UX designer / UI designer / visual polish / design system spec”或“开发工程师 / 技术方案 / 开发方案 / 实现 / 实现方案 / 实施计划 / 技术专家 / software engineer / development plan / technical implementation plan / 多开agent / subagent”这类角色或强产物名时,使用对应 Context authoring Skill,把长期结论写回 `project_context/**`。
|
|
41
|
+
12. 用户明确要求“导出尽可能详细的项目全量上下文 / 全量上下文导出 / full project context export / export full project context / 当前项目代码实现 / 代码级实现导出 / code-level implementation export”时,使用 `context_full_project_export` Skill;默认优先运行 `sdlc-harness export-context --all` 同时生成项目级 Context 汇总和代码级实现快照;只需要单份产物时再用 `--full` 或 `--code`;导出产物只放 `tmp/sdlc/context-exports/**`,不得放入或注册到 `project_context/**` / `project_context/context.toml`。用户明确要求“upgrade Tiny Context / update Tiny Context / Project Tiny Context Harness upgrade / 用 Tiny Context upgrade skill 升级这个项目 / 升级 tiny context”时,使用 `context_harness_upgrade` Skill,先走 `upgrade`,不要先单独运行 `sync`。
|
|
42
42
|
13. 当任务涉及设计稿、重做设计、视觉方案、设计系统、visual polish、frontend redesign 或 frontend styling,且存在可扫描的 UI 代码、页面文件、构建产物目录或本地/远程 URL 时,默认运行 `npx impeccable detect <target>` 做 Impeccable 视觉审查;没有可扫描目标、命令不可用或扫描失败时,说明原因并继续。Impeccable 不是 `validate-context` gate,也不替代截图检查、项目测试或人工判断。
|
|
43
|
-
14. SDLC / Harness managed surfaces 是生成资产:`AGENTS.md` managed block、`.agent/pjsdlc_managed/**`、`.agent/skills/context_product_plan/**`、`.agent/skills/context_uiux_design/**`、`.agent/skills/context_development_engineer/**` 和 `.agent/skills/
|
|
43
|
+
14. SDLC / Harness managed surfaces 是生成资产:`AGENTS.md` managed block、`.agent/pjsdlc_managed/**`、`.agent/skills/context_product_plan/**`、`.agent/skills/context_uiux_design/**`、`.agent/skills/context_development_engineer/**`、`.agent/skills/context_full_project_export/**` 和 `.agent/skills/context_harness_upgrade/**` 禁止承载项目特定规则;直接编辑会在 `sync` 时被覆盖或产生漂移。项目本地产品 / UIUX / 开发规则必须新建独立 Skill,例如 `.agent/skills/product_plan/SKILL.md`、`.agent/skills/uiux_design/SKILL.md` 或 `.agent/skills/development_engineer/SKILL.md`;当项目本地 Skill 与默认 Skill 同时适用时,优先使用更具体的项目本地 Skill。项目本地 Skill 的 front matter `description` 触发词应与本文件中的角色触发规则和对应默认 `context_*` Skill 保持一致;新增或收窄关键词时,同步更新本地 Skill 描述和项目级 agent 指引,避免 Skill 触发条件与 SDLC 工作规则漂移。
|
|
44
44
|
15. ADR 降级为 Context 中的 `Design Rationale`;实现说明优先写成代码注释、测试名或模块 Context 中的关键约束。
|
|
45
45
|
16. Harness workflow gate 只运行 `validate-context`,用于检查上下文是否可恢复;不检查 context/code 修改顺序。自动化最多提示 context-first 风险,不做阻断。
|
|
46
46
|
17. 产品质量由项目自己的验证入口证明;Context 只能声明验证 / 部署关键路径,不能伪造“测试已通过”或“部署已成功”。
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
PYTHON ?= python3
|
|
2
2
|
SDLC_HARNESS ?= $(if $(wildcard packages/sdlc-harness/dist/cli.js),node packages/sdlc-harness/dist/cli.js,npx --yes --package project-tiny-context-harness@latest sdlc-harness)
|
|
3
3
|
|
|
4
|
-
.PHONY: help sdlc-doctor sdlc-sync sdlc-upgrade validate-context validate-harness lint test-current-domain test-all build
|
|
4
|
+
.PHONY: help sdlc-doctor sdlc-sync sdlc-upgrade sdlc-check-modularity validate-context validate-harness lint test-current-domain test-all build
|
|
5
5
|
|
|
6
6
|
help:
|
|
7
7
|
@echo "Minimal Context Harness commands"
|
|
8
8
|
@echo " make sdlc-doctor Diagnose Harness root, core package and schema version"
|
|
9
9
|
@echo " make sdlc-sync Refresh managed assets; refuses when upgrade migrations are pending"
|
|
10
10
|
@echo " make sdlc-upgrade Run safe upgrade migrations, sync managed assets and doctor"
|
|
11
|
+
@echo " make sdlc-check-modularity Warn on oversized touched handwritten source files"
|
|
11
12
|
@echo " make validate-context Check whether project_context/** supports context recovery"
|
|
12
13
|
@echo " make validate-harness Compatibility alias for validate-context"
|
|
13
14
|
@echo " make test-all Run the project regression suite after replacing this placeholder"
|
|
@@ -18,11 +19,14 @@ sdlc-doctor:
|
|
|
18
19
|
sdlc-sync:
|
|
19
20
|
$(SDLC_HARNESS) sync
|
|
20
21
|
|
|
21
|
-
sdlc-upgrade:
|
|
22
|
-
$(SDLC_HARNESS) upgrade
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
$(SDLC_HARNESS)
|
|
22
|
+
sdlc-upgrade:
|
|
23
|
+
$(SDLC_HARNESS) upgrade
|
|
24
|
+
|
|
25
|
+
sdlc-check-modularity:
|
|
26
|
+
$(SDLC_HARNESS) check-modularity --touched
|
|
27
|
+
|
|
28
|
+
validate-context:
|
|
29
|
+
$(SDLC_HARNESS) validate-context
|
|
26
30
|
|
|
27
31
|
validate-harness: validate-context
|
|
28
32
|
|
|
@@ -33,6 +33,9 @@ Project-specific engineering rules belong in a separate project-local Skill unde
|
|
|
33
33
|
10. 实现时保持精准修改,优先遵循仓库现有框架、接口、测试和代码风格。
|
|
34
34
|
11. 当用户明确要求 / 允许“多开agent”或使用 subagent,且当前会话存在可用 subagent 工具时,积极把可并行的探索、审查或实现拆分交给 subagent;使用前先复用已有相关 agent,没有合适 agent 或并行度不足时再新开。`wait_agent` 只表示取得结果,不释放资源;subagent 完成、空闲或不再需要时必须调用 `close_agent`,收尾前清理已完成 / 空闲 / 不再需要的 subagent,避免占满后续资源。
|
|
35
35
|
12. 当任务涉及新实现、重构、重复逻辑、模块边界或影响面控制时,先做轻量 abstraction / decomposition scan:
|
|
36
|
+
- 工程 / RFC / 实现类 Task Contract 包含 `Modularity Check: none|required|exception`;可用 `sdlc-harness check-modularity --file <path> --limit 300` 审计计划编辑文件,用 `sdlc-harness check-modularity --touched --limit 300` 做交付前审计。若项目本地 Skill 定义了不同 limit,使用项目本地值。
|
|
37
|
+
- 发现超限 touched file 后,不只记录行数;判断本次是否在该文件加入新职责,并回到本节拆分原则选择产品面、hook、model、adapter、component、service / facade 或 verification helper 等边界。避免只按行数机械拆分、但耦合和职责仍留在原处。
|
|
38
|
+
- 如果本次不拆,`Modularity Check` 取 `exception`,交付说明写 `Modularity: exception documented` 并列出文件、暂不拆原因和后续拆分边界。
|
|
36
39
|
- 查找相似实现、重复逻辑、紧耦合模块或影响面异常扩散点。
|
|
37
40
|
- 当一个业务对象、能力或接口的变更需要跨多个 Context、产品域或实现层同步调整时,将该影响范围视为模块边界复核信号;优先评估是否应通过独立模块、服务、facade 或稳定接口收敛依赖,避免通过手工 manifest 长期复制实现暴露面。
|
|
38
41
|
- 将候选项分为局部重构与长期边界变化,后者按既有 Context-first 规则处理。
|
|
@@ -63,6 +66,10 @@ Project-specific engineering rules belong in a separate project-local Skill unde
|
|
|
63
66
|
- `none`:本次只是按既有 Context / 架构原则落地,不新增长期事实。
|
|
64
67
|
- `required`:说明长期事实类型、应写入的 Context / role、需要沉淀的事实,以及明确不写入 Context 的一次性内容。
|
|
65
68
|
- `Task Contract` 用短列表说明 capability、owner、upstream / downstream、allowed / forbidden dependency、input / output / state / persistence、failure / retry / timeout / degraded / recovery、observability、performance、security、non-goals 和 verification path。
|
|
69
|
+
- 工程 / RFC / 实现类任务的 `Task Contract` 必须包含 `Modularity Check: none|required|exception`:
|
|
70
|
+
- `none`:没有超限计划 / touched 手写源码文件,或本次没有向超限文件增加新职责。
|
|
71
|
+
- `required`:拆分是本次验收条件,应按 abstraction / decomposition scan 的职责边界完成。
|
|
72
|
+
- `exception`:本次触碰超限文件但暂不拆;交付说明必须记录文件、原因和后续拆分边界。
|
|
66
73
|
- `Applicable Module Design` 是高风险任务的前置字段:列出命中的 Context / Skill 来源、适用的 Principles、Design Logic 和 Design Rationale,以及它们控制的当前实现或验证选择。
|
|
67
74
|
- `Principle Decision Gate` 要写明首选执行路径、fallback / degraded path 的进入条件,以及什么证据不能证明本次目标。涉及 capability、metric 或 acceptance claim 时,先声明要证明的 claim,再选择命令或 probe。
|
|
68
75
|
- 对长任务、多模块、多 agent、容易发生 `Context Delta` 调头或多轮验证的任务,可以用 `plan.md` 或等价临时计划面暂存 `Context Delta`、`Task Contract`、`Implementation Steps` 和 `Contract Conformance`;它只是临时执行缓存。
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: context_harness_upgrade
|
|
3
|
+
description: Use when the user asks to upgrade Tiny Context, update Tiny Context, use the Tiny Context upgrade skill to upgrade this project, upgrade project-tiny-context-harness, run sdlc-harness upgrade, run make sdlc-upgrade, Project Tiny Context Harness upgrade, 用 Tiny Context upgrade skill 升级这个项目, 升级 tiny context, 升级tiny context, or 更新 project-tiny-context-harness in a Minimal Context Harness project.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Context Harness Upgrade
|
|
7
|
+
|
|
8
|
+
## Package-Managed Boundary
|
|
9
|
+
|
|
10
|
+
This Skill is generated by `sdlc-harness sync` and owned by the Harness package. Do not edit the generated `context_harness_upgrade` Skill directly.
|
|
11
|
+
|
|
12
|
+
This Skill handles Harness package upgrade and migration orchestration only. It does not author product facts, refactor business code, replace project tests or prove application quality.
|
|
13
|
+
|
|
14
|
+
## Purpose
|
|
15
|
+
|
|
16
|
+
When the user asks to upgrade Tiny Context / Project Tiny Context Harness in an existing project, run the canonical upgrade path, handle only migration-scoped follow-up, and leave durable project semantics to the project Context and user-owned code.
|
|
17
|
+
|
|
18
|
+
## Workflow
|
|
19
|
+
|
|
20
|
+
1. Read the project routing and Context entry points before changing files:
|
|
21
|
+
- `AGENTS.md`
|
|
22
|
+
- `project_context/global.md`
|
|
23
|
+
- `project_context/architecture.md`
|
|
24
|
+
- `project_context/context.toml`
|
|
25
|
+
2. Inspect the working tree with `git status --short`. Do not revert unrelated user changes.
|
|
26
|
+
3. Prefer project wrappers when present:
|
|
27
|
+
- `make sdlc-upgrade`
|
|
28
|
+
- `make sdlc-doctor`
|
|
29
|
+
- `make validate-context`
|
|
30
|
+
4. If no wrapper exists, use the package CLI explicitly:
|
|
31
|
+
- `npx --yes --package project-tiny-context-harness@latest sdlc-harness upgrade --check`
|
|
32
|
+
- `npx --yes --package project-tiny-context-harness@latest sdlc-harness upgrade`
|
|
33
|
+
- `npx --yes --package project-tiny-context-harness@latest sdlc-harness doctor`
|
|
34
|
+
5. Do not run standalone `sync` before `upgrade`. `upgrade` is the default path after an npm package update because it plans migrations, applies safe migrations, refreshes managed assets and runs diagnostics.
|
|
35
|
+
6. Do not run standalone `sync` after a successful `upgrade` unless release notes say the update is `sync-only`, the project wrapper did not run sync, or the user explicitly asks for a managed-asset refresh.
|
|
36
|
+
7. If `upgrade --check` or `upgrade` reports only `safe_pending` items and the command succeeds, do not invent additional manual cleanup.
|
|
37
|
+
8. If the report includes `manual_required` or `blocked`, handle only the listed migration scope. Use `project_context/context.toml`, role placement scan and the existing area graph to decide placement. Do not guess product or business semantics.
|
|
38
|
+
9. If the report includes `blocked`, treat it as a write preflight failure: resolve the blocked migration scope and rerun `upgrade` before expecting safe migrations or managed asset sync to have been applied.
|
|
39
|
+
10. Run diagnostics after migration-scoped follow-up:
|
|
40
|
+
- `make sdlc-doctor` or the CLI `doctor`
|
|
41
|
+
- `make validate-context`
|
|
42
|
+
11. Report commands run, migration status, diagnostics, files changed and any remaining manual items. Use `Context: no durable project facts changed` unless the upgrade exposed or required a real long-term project fact change.
|
|
43
|
+
|
|
44
|
+
## Manual Handling Rules
|
|
45
|
+
|
|
46
|
+
- `manual_required` means the Harness detected a migration-scoped item but cannot safely choose the project meaning. Fix it only when the existing Context or file structure proves the intended placement.
|
|
47
|
+
- `blocked` means the safe target already exists or another conflict prevents a mechanical write. Do not overwrite. Compare source and target, preserve user content and ask for direction if the conflict cannot be resolved from project facts.
|
|
48
|
+
- Deprecated override Skills under `pjsdlc_managed/override_skills/**` should be migrated to standalone project-local Skills only when their content is still relevant. Do not merge them into package-managed default Skills.
|
|
49
|
+
- Ambiguous Context roles should not be guessed from filenames like `api.md`, `notes.md` or `main.md` alone. Register a role only when the manifest, path convention or durable content makes it clear.
|
|
50
|
+
- If `upgrade` creates default `project_context/areas/main.md` or `project_context/areas/main/verification.md` but `project_context/context.toml` already declares real project areas and does not register `main`, remove those generated defaults instead of preserving them as durable project facts. If `main` is registered or contains user-authored facts, do not delete it automatically.
|
|
51
|
+
|
|
52
|
+
## Boundaries
|
|
53
|
+
|
|
54
|
+
- Do not restore legacy stage workflow, sprint documents or phase gates.
|
|
55
|
+
- Do not modify application code, product behavior, deployment settings or tests unless the user explicitly asks or a migration report points to a Harness-owned file in that scope.
|
|
56
|
+
- Do not write one-off command output, release logs, diagnostics or temporary migration notes into `project_context/**`.
|
|
57
|
+
- Do not edit package-managed generated surfaces directly in a consumer project unless the change is part of a migration conflict that cannot be resolved another way. Prefer rerunning `upgrade` / `sync` from the package source.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function checkModularity(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { runModularityCheck } from "../lib/modularity.js";
|
|
2
|
+
export async function checkModularity(args) {
|
|
3
|
+
let parsed;
|
|
4
|
+
try {
|
|
5
|
+
parsed = parseArgs(args);
|
|
6
|
+
}
|
|
7
|
+
catch (error) {
|
|
8
|
+
console.error(`error: ${error instanceof Error ? error.message : String(error)}`);
|
|
9
|
+
process.exitCode = 1;
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (parsed.help || (!parsed.touched && !parsed.base && parsed.files.length === 0)) {
|
|
13
|
+
console.log(helpText());
|
|
14
|
+
if (!parsed.help) {
|
|
15
|
+
process.exitCode = 1;
|
|
16
|
+
}
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const report = await runModularityCheck(process.cwd(), {
|
|
21
|
+
touched: parsed.touched,
|
|
22
|
+
base: parsed.base,
|
|
23
|
+
files: parsed.files,
|
|
24
|
+
limit: parsed.limit
|
|
25
|
+
});
|
|
26
|
+
console.log(`check-modularity audited=${report.files.length} warning=${report.warnings.length} limit=${report.limit}`);
|
|
27
|
+
if (report.files.length === 0) {
|
|
28
|
+
console.log("No handwritten source files matched the selected scope.");
|
|
29
|
+
}
|
|
30
|
+
for (const file of report.files) {
|
|
31
|
+
const prefix = file.overLimit ? "over-limit" : "ok";
|
|
32
|
+
console.log(`${prefix}: ${file.relativePath} ${file.lines} lines`);
|
|
33
|
+
}
|
|
34
|
+
for (const warning of report.warnings) {
|
|
35
|
+
console.warn(`warning: ${warning}`);
|
|
36
|
+
}
|
|
37
|
+
if (report.warnings.length > 0) {
|
|
38
|
+
console.warn("warning: over-limit touched files need a split, or final handoff should include `Modularity: exception documented` with file, reason and future boundary.");
|
|
39
|
+
}
|
|
40
|
+
if (report.warnings.length > 0 && parsed.failOnWarning) {
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
console.error(`error: ${error instanceof Error ? error.message : String(error)}`);
|
|
46
|
+
process.exitCode = 1;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function parseArgs(args) {
|
|
50
|
+
const parsed = {
|
|
51
|
+
touched: false,
|
|
52
|
+
files: [],
|
|
53
|
+
limit: 300,
|
|
54
|
+
failOnWarning: false,
|
|
55
|
+
help: false
|
|
56
|
+
};
|
|
57
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
58
|
+
const arg = args[index];
|
|
59
|
+
if (arg === "--touched") {
|
|
60
|
+
parsed.touched = true;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (arg === "--fail-on-warning") {
|
|
64
|
+
parsed.failOnWarning = true;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (arg === "--help" || arg === "-h") {
|
|
68
|
+
parsed.help = true;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (arg === "--file") {
|
|
72
|
+
const value = args[index + 1];
|
|
73
|
+
if (!value || value.startsWith("--")) {
|
|
74
|
+
throw new Error("check-modularity --file requires a path");
|
|
75
|
+
}
|
|
76
|
+
parsed.files.push(value);
|
|
77
|
+
index += 1;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (arg.startsWith("--file=")) {
|
|
81
|
+
const value = arg.slice("--file=".length).trim();
|
|
82
|
+
if (!value) {
|
|
83
|
+
throw new Error("check-modularity --file requires a path");
|
|
84
|
+
}
|
|
85
|
+
parsed.files.push(value);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (arg === "--base") {
|
|
89
|
+
const value = args[index + 1];
|
|
90
|
+
if (!value || value.startsWith("--")) {
|
|
91
|
+
throw new Error("check-modularity --base requires a ref");
|
|
92
|
+
}
|
|
93
|
+
parsed.base = value;
|
|
94
|
+
index += 1;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (arg.startsWith("--base=")) {
|
|
98
|
+
const value = arg.slice("--base=".length).trim();
|
|
99
|
+
if (!value) {
|
|
100
|
+
throw new Error("check-modularity --base requires a ref");
|
|
101
|
+
}
|
|
102
|
+
parsed.base = value;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (arg === "--limit") {
|
|
106
|
+
const value = args[index + 1];
|
|
107
|
+
if (!value || value.startsWith("--")) {
|
|
108
|
+
throw new Error("check-modularity --limit requires a positive integer");
|
|
109
|
+
}
|
|
110
|
+
parsed.limit = parseLimit(value);
|
|
111
|
+
index += 1;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (arg.startsWith("--limit=")) {
|
|
115
|
+
parsed.limit = parseLimit(arg.slice("--limit=".length));
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
throw new Error(`unknown check-modularity argument: ${arg}`);
|
|
119
|
+
}
|
|
120
|
+
return parsed;
|
|
121
|
+
}
|
|
122
|
+
function parseLimit(value) {
|
|
123
|
+
const limit = Number.parseInt(value, 10);
|
|
124
|
+
if (!Number.isInteger(limit) || limit <= 0 || String(limit) !== value.trim()) {
|
|
125
|
+
throw new Error("check-modularity --limit requires a positive integer");
|
|
126
|
+
}
|
|
127
|
+
return limit;
|
|
128
|
+
}
|
|
129
|
+
function helpText() {
|
|
130
|
+
return `sdlc-harness check-modularity:
|
|
131
|
+
check-modularity --touched [--limit 300] [--fail-on-warning]
|
|
132
|
+
check-modularity --file <path> [--file <path> ...] [--limit 300] [--fail-on-warning]
|
|
133
|
+
check-modularity --base <ref> [--limit 300] [--fail-on-warning]
|
|
134
|
+
|
|
135
|
+
Audits selected handwritten source files for physical line-count risk.
|
|
136
|
+
The default is warning-only; --fail-on-warning lets projects opt into CI enforcement.`;
|
|
137
|
+
}
|
package/dist/commands/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { checkModularity } from "./check-modularity.js";
|
|
1
2
|
import { doctor } from "./doctor.js";
|
|
2
3
|
import { exportContext } from "./export-context.js";
|
|
3
4
|
import { init } from "./init.js";
|
|
@@ -11,6 +12,7 @@ export const commands = {
|
|
|
11
12
|
sync,
|
|
12
13
|
upgrade,
|
|
13
14
|
doctor,
|
|
15
|
+
"check-modularity": checkModularity,
|
|
14
16
|
"export-context": exportContext,
|
|
15
17
|
validate,
|
|
16
18
|
"validate-context": (args) => validate(["validate-context", ...args]),
|
|
@@ -24,8 +26,10 @@ export function help() {
|
|
|
24
26
|
sync Refresh managed assets; refuses when upgrade migrations are pending
|
|
25
27
|
upgrade [--check] [--json]
|
|
26
28
|
Run safe migrations, sync managed assets and doctor
|
|
27
|
-
doctor Diagnose project configuration and drift
|
|
28
|
-
|
|
29
|
+
doctor Diagnose project configuration and drift
|
|
30
|
+
check-modularity --touched|--file <path>|--base <ref> [--limit 300] [--fail-on-warning]
|
|
31
|
+
Warn when selected handwritten source files exceed a line-count limit
|
|
32
|
+
export-context --full|--code|--all [--output <path>] [--check]
|
|
29
33
|
Export a temporary Context summary or code implementation Markdown artifact
|
|
30
34
|
validate <gate> Run a Harness validation gate (Minimal Context only)
|
|
31
35
|
validate-context Validate Minimal Context fact-source recoverability
|
package/dist/commands/upgrade.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createUpgradePlan, formatUpgradePlan, hasUpgradePlanWork, updateModeForPlan } from "../lib/migrations.js";
|
|
2
|
-
import {
|
|
2
|
+
import { runUpgradeReport } from "../lib/upgrade.js";
|
|
3
3
|
export async function upgrade(args = []) {
|
|
4
4
|
const options = parseArgs(args);
|
|
5
5
|
if (options.help) {
|
|
@@ -21,14 +21,20 @@ export async function upgrade(args = []) {
|
|
|
21
21
|
}
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
|
-
const report = await
|
|
24
|
+
const report = await runUpgradeReport(process.cwd());
|
|
25
25
|
if (options.json) {
|
|
26
|
-
console.log(JSON.stringify(
|
|
26
|
+
console.log(JSON.stringify(report, null, 2));
|
|
27
|
+
if (report.blocked) {
|
|
28
|
+
process.exitCode = 1;
|
|
29
|
+
}
|
|
27
30
|
return;
|
|
28
31
|
}
|
|
29
|
-
for (const line of report) {
|
|
32
|
+
for (const line of report.lines) {
|
|
30
33
|
console.log(line);
|
|
31
34
|
}
|
|
35
|
+
if (report.blocked) {
|
|
36
|
+
process.exitCode = 1;
|
|
37
|
+
}
|
|
32
38
|
}
|
|
33
39
|
function parseArgs(args) {
|
|
34
40
|
const options = { check: false, json: false, help: false };
|
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import { ensureDir, listFiles, pathExists, readText, writeTextIfChanged } from "./fs.js";
|
|
7
7
|
import { harnessRoot } from "./harness-root.js";
|
|
8
|
+
import { SAFE_EXAMPLE_FILE_NAMES, shouldExcludeRelativePath, shouldIncludeCodeFile, toPosix } from "./source-files.js";
|
|
8
9
|
const execFileAsync = promisify(execFile);
|
|
9
10
|
const EXPORT_HEADER = "Export artifact. Do not reference from project_context/context.toml.";
|
|
10
11
|
const DEFAULT_EXPORT_DIR = "tmp/sdlc/context-exports";
|
|
@@ -13,91 +14,6 @@ const MAX_TREE_ENTRIES = 300;
|
|
|
13
14
|
const MAX_TREE_DEPTH = 4;
|
|
14
15
|
const GIT_LS_MAX_BUFFER = 64 * 1024 * 1024;
|
|
15
16
|
const APPROX_TEXT_TOKEN_LIMIT_CHARS = 8_000_000;
|
|
16
|
-
const EXCLUDED_DIR_NAMES = new Set([
|
|
17
|
-
".git",
|
|
18
|
-
".artifacts",
|
|
19
|
-
".cache",
|
|
20
|
-
".next",
|
|
21
|
-
".nuxt",
|
|
22
|
-
".runtime",
|
|
23
|
-
".turbo",
|
|
24
|
-
"artifacts",
|
|
25
|
-
"build",
|
|
26
|
-
"cache",
|
|
27
|
-
"captures",
|
|
28
|
-
"coverage",
|
|
29
|
-
"dist",
|
|
30
|
-
"logs",
|
|
31
|
-
"node_modules",
|
|
32
|
-
"out",
|
|
33
|
-
"playwright-report",
|
|
34
|
-
"raw-captures",
|
|
35
|
-
"reports",
|
|
36
|
-
"target",
|
|
37
|
-
"temp",
|
|
38
|
-
"test-reports",
|
|
39
|
-
"test-results",
|
|
40
|
-
"tmp"
|
|
41
|
-
]);
|
|
42
|
-
const SAFE_EXAMPLE_FILE_NAMES = new Set([".env.example", ".env.sample", ".env.template", "example.env", "sample.env"]);
|
|
43
|
-
const EXCLUDED_FILE_PATTERNS = [
|
|
44
|
-
/^\.env(?:\.|$)/i,
|
|
45
|
-
/\.log$/i,
|
|
46
|
-
/\.min\.(?:css|js|mjs)$/i,
|
|
47
|
-
/(^|[-_.])(secret|secrets|cookie|cookies|credential|credentials|api-key|apikey|access-token|refresh-token|auth-token|private-key)([-_.]|$)/i,
|
|
48
|
-
/(^|[-_.])(raw-capture|capture-dump|licensed-payload|license-payload|test-report)([-_.]|$)/i,
|
|
49
|
-
/^(?:package-lock\.json|npm-shrinkwrap\.json|pnpm-lock\.yaml|yarn\.lock|poetry\.lock|pipfile\.lock|cargo\.lock)$/i,
|
|
50
|
-
/full-project-context-\d{8}T\d{6}Z\.md$/i,
|
|
51
|
-
/当前项目context-\d{8}T\d{6}Z\.md$/i,
|
|
52
|
-
/当前项目代码实现\.md$/i,
|
|
53
|
-
/(^|[-_.])(code-level-implementation|context-export|context-bundle)([-_.]|$)/i
|
|
54
|
-
];
|
|
55
|
-
const CODE_FILE_EXTENSIONS = [
|
|
56
|
-
".bat",
|
|
57
|
-
".cjs",
|
|
58
|
-
".cmd",
|
|
59
|
-
".go",
|
|
60
|
-
".gql",
|
|
61
|
-
".graphql",
|
|
62
|
-
".js",
|
|
63
|
-
".jsx",
|
|
64
|
-
".jsonc",
|
|
65
|
-
".mjs",
|
|
66
|
-
".proto",
|
|
67
|
-
".ps1",
|
|
68
|
-
".py",
|
|
69
|
-
".sh",
|
|
70
|
-
".sql",
|
|
71
|
-
".toml",
|
|
72
|
-
".ts",
|
|
73
|
-
".tsx",
|
|
74
|
-
".vue",
|
|
75
|
-
".yaml",
|
|
76
|
-
".yml"
|
|
77
|
-
];
|
|
78
|
-
const CODE_FILE_BASE_NAMES = new Set([
|
|
79
|
-
".env.example",
|
|
80
|
-
".env.sample",
|
|
81
|
-
".env.template",
|
|
82
|
-
"dockerfile",
|
|
83
|
-
"makefile",
|
|
84
|
-
"package.json",
|
|
85
|
-
"pyproject.toml",
|
|
86
|
-
"requirements.txt",
|
|
87
|
-
"setup.cfg",
|
|
88
|
-
"tsconfig.json"
|
|
89
|
-
]);
|
|
90
|
-
const CONFIG_JSON_NAMES = new Set([
|
|
91
|
-
"babel.config.json",
|
|
92
|
-
"biome.json",
|
|
93
|
-
"composer.json",
|
|
94
|
-
"deno.json",
|
|
95
|
-
"eslint.config.json",
|
|
96
|
-
"jsconfig.json",
|
|
97
|
-
"package.json",
|
|
98
|
-
"tsconfig.json",
|
|
99
|
-
"vite.config.json"
|
|
100
|
-
]);
|
|
101
17
|
const SENSITIVE_ASSIGNMENT_PATTERN = /^(\s*(?:[-*]\s*)?(?:[`"']?[\w.-]*(?:secret|token|cookie|password|api[_-]?key)[\w.-]*[`"']?\s*[:=]\s*))(.+?)\s*$/i;
|
|
102
18
|
export async function runExportContext(projectRoot, options = {}) {
|
|
103
19
|
const requestedModeCount = Number(options.full === true) + Number(options.code === true);
|
|
@@ -519,55 +435,6 @@ function buildImplementationGuide(records) {
|
|
|
519
435
|
"- This file is a temporary implementation snapshot, not durable Context; durable project facts still belong in project_context/**."
|
|
520
436
|
].join("\n");
|
|
521
437
|
}
|
|
522
|
-
function shouldIncludeCodeFile(relative) {
|
|
523
|
-
if (shouldExcludeRelativePath(relative)) {
|
|
524
|
-
return false;
|
|
525
|
-
}
|
|
526
|
-
const normalized = toPosix(relative);
|
|
527
|
-
const lower = normalized.toLowerCase();
|
|
528
|
-
const base = path.posix.basename(lower);
|
|
529
|
-
if (CODE_FILE_BASE_NAMES.has(base) || base.startsWith("dockerfile.")) {
|
|
530
|
-
return true;
|
|
531
|
-
}
|
|
532
|
-
if (lower.endsWith(".dockerfile")) {
|
|
533
|
-
return true;
|
|
534
|
-
}
|
|
535
|
-
if (lower.endsWith(".json")) {
|
|
536
|
-
return isConfigJson(lower);
|
|
537
|
-
}
|
|
538
|
-
return CODE_FILE_EXTENSIONS.some((extension) => lower.endsWith(extension));
|
|
539
|
-
}
|
|
540
|
-
function shouldExcludeRelativePath(relative) {
|
|
541
|
-
const normalized = toPosix(relative);
|
|
542
|
-
const segments = normalized.split("/");
|
|
543
|
-
if (segments.some((segment) => EXCLUDED_DIR_NAMES.has(segment))) {
|
|
544
|
-
return true;
|
|
545
|
-
}
|
|
546
|
-
const base = segments[segments.length - 1] ?? "";
|
|
547
|
-
const lowerBase = base.toLowerCase();
|
|
548
|
-
return EXCLUDED_FILE_PATTERNS.some((pattern) => {
|
|
549
|
-
if (SAFE_EXAMPLE_FILE_NAMES.has(lowerBase) && pattern.source.startsWith("^\\.env")) {
|
|
550
|
-
return false;
|
|
551
|
-
}
|
|
552
|
-
return pattern.test(base) || pattern.test(normalized);
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
function isConfigJson(lowerRelative) {
|
|
556
|
-
const base = path.posix.basename(lowerRelative);
|
|
557
|
-
return (CONFIG_JSON_NAMES.has(base) ||
|
|
558
|
-
lowerRelative.endsWith(".schema.json") ||
|
|
559
|
-
lowerRelative.includes("/schema/") ||
|
|
560
|
-
lowerRelative.includes("/schemas/") ||
|
|
561
|
-
lowerRelative.includes("/config/") ||
|
|
562
|
-
lowerRelative.includes("/configs/") ||
|
|
563
|
-
lowerRelative.includes("/examples/") ||
|
|
564
|
-
lowerRelative.includes("/sample/") ||
|
|
565
|
-
lowerRelative.includes("/samples/") ||
|
|
566
|
-
base.includes("config") ||
|
|
567
|
-
base.includes("schema") ||
|
|
568
|
-
base.includes("example") ||
|
|
569
|
-
base.includes("sample"));
|
|
570
|
-
}
|
|
571
438
|
function summarizeCodeFile(relative, content, language) {
|
|
572
439
|
const lower = relative.toLowerCase();
|
|
573
440
|
const base = path.posix.basename(relative);
|
|
@@ -837,9 +704,6 @@ function timestampForFile(now) {
|
|
|
837
704
|
function repoRelative(root, file) {
|
|
838
705
|
return toPosix(path.relative(root, file));
|
|
839
706
|
}
|
|
840
|
-
function toPosix(value) {
|
|
841
|
-
return value.replace(/\\/g, "/");
|
|
842
|
-
}
|
|
843
707
|
function escapeTableCell(value) {
|
|
844
708
|
return value.replace(/\r?\n/g, " ").replace(/\|/g, "\\|");
|
|
845
709
|
}
|
package/dist/lib/migrations.d.ts
CHANGED
|
@@ -36,4 +36,4 @@ export declare function createUpgradePlan(projectRoot: string): Promise<UpgradeP
|
|
|
36
36
|
export declare function hasUpgradePlanWork(plan: UpgradePlan): boolean;
|
|
37
37
|
export declare function updateModeForPlan(plan: UpgradePlan): ReleaseUpdateMode;
|
|
38
38
|
export declare function formatUpgradePlan(plan: UpgradePlan): string[];
|
|
39
|
-
export declare function runMigrations(projectRoot: string): Promise<MigrationReport>;
|
|
39
|
+
export declare function runMigrations(projectRoot: string, existingPlan?: UpgradePlan): Promise<MigrationReport>;
|
package/dist/lib/migrations.js
CHANGED
|
@@ -112,12 +112,15 @@ export function formatUpgradePlan(plan) {
|
|
|
112
112
|
}
|
|
113
113
|
return lines;
|
|
114
114
|
}
|
|
115
|
-
export async function runMigrations(projectRoot) {
|
|
115
|
+
export async function runMigrations(projectRoot, existingPlan) {
|
|
116
116
|
const report = { changed: [], skipped: [], manualRequired: [], blocked: [] };
|
|
117
117
|
const root = await harnessRoot(projectRoot);
|
|
118
|
-
const plan = await createUpgradePlan(projectRoot);
|
|
118
|
+
const plan = existingPlan ?? (await createUpgradePlan(projectRoot));
|
|
119
119
|
report.manualRequired.push(...plan.manual_required);
|
|
120
120
|
report.blocked.push(...plan.blocked);
|
|
121
|
+
if (plan.blocked.length > 0) {
|
|
122
|
+
return report;
|
|
123
|
+
}
|
|
121
124
|
for (const migration of migrations) {
|
|
122
125
|
if (!migration.apply) {
|
|
123
126
|
continue;
|
|
@@ -137,10 +140,14 @@ async function migrateBaseProjectContext(projectRoot, report) {
|
|
|
137
140
|
await ensureDir(path.join(projectRoot, "project_context", "areas"));
|
|
138
141
|
const files = [
|
|
139
142
|
["project_context/global.md", globalContextTemplate()],
|
|
140
|
-
["project_context/architecture.md", architectureContextTemplate()]
|
|
141
|
-
["project_context/areas/main.md", areaContextTemplate("main")],
|
|
142
|
-
["project_context/areas/main/verification.md", verificationContextTemplate("main")]
|
|
143
|
+
["project_context/architecture.md", architectureContextTemplate()]
|
|
143
144
|
];
|
|
145
|
+
if (await contextManifestReferences(projectRoot, "project_context/areas/main.md")) {
|
|
146
|
+
files.push(["project_context/areas/main.md", areaContextTemplate("main")]);
|
|
147
|
+
}
|
|
148
|
+
if (await contextManifestReferences(projectRoot, "project_context/areas/main/verification.md")) {
|
|
149
|
+
files.push(["project_context/areas/main/verification.md", verificationContextTemplate("main")]);
|
|
150
|
+
}
|
|
144
151
|
for (const [relative, content] of files) {
|
|
145
152
|
const target = path.join(projectRoot, ...relative.split("/"));
|
|
146
153
|
if (await pathExists(target)) {
|
|
@@ -155,6 +162,13 @@ async function migrateBaseProjectContext(projectRoot, report) {
|
|
|
155
162
|
}
|
|
156
163
|
}
|
|
157
164
|
}
|
|
165
|
+
async function contextManifestReferences(projectRoot, relative) {
|
|
166
|
+
const manifestPath = path.join(projectRoot, CONTEXT_MANIFEST_PATH);
|
|
167
|
+
if (!(await pathExists(manifestPath))) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
return manifestReferencesPath(await readText(manifestPath), relative);
|
|
171
|
+
}
|
|
158
172
|
async function detectGlobalContextSections(projectRoot, _root, migration) {
|
|
159
173
|
const relative = "project_context/global.md";
|
|
160
174
|
const target = path.join(projectRoot, ...relative.split("/"));
|
|
@@ -427,3 +441,7 @@ function ensureManifestDefaultArea(content) {
|
|
|
427
441
|
lines.splice(nextTableIndex, 0, "default = true");
|
|
428
442
|
return lines.join("\n");
|
|
429
443
|
}
|
|
444
|
+
function manifestReferencesPath(content, relative) {
|
|
445
|
+
const escaped = relative.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
446
|
+
return new RegExp(`^(?:\\s*)(?:context|path)\\s*=\\s*["']${escaped}["']\\s*$`, "im").test(content);
|
|
447
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ModularityCheckOptions {
|
|
2
|
+
touched?: boolean;
|
|
3
|
+
base?: string;
|
|
4
|
+
files?: string[];
|
|
5
|
+
limit?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface ModularityFileReport {
|
|
8
|
+
relativePath: string;
|
|
9
|
+
lines: number;
|
|
10
|
+
overLimit: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface ModularityCheckReport {
|
|
13
|
+
limit: number;
|
|
14
|
+
files: ModularityFileReport[];
|
|
15
|
+
warnings: string[];
|
|
16
|
+
}
|
|
17
|
+
export declare function runModularityCheck(projectRoot: string, options: ModularityCheckOptions): Promise<ModularityCheckReport>;
|
|
18
|
+
export declare function countPhysicalLines(content: string): number;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { shouldIncludeCodeFile, toPosix } from "./source-files.js";
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
const GIT_MAX_BUFFER = 16 * 1024 * 1024;
|
|
8
|
+
const GENERATED_FILE_PATTERNS = [
|
|
9
|
+
/^\s*(?:\/\/|#|--|;)\s*@generated\b/im,
|
|
10
|
+
/^\s*(?:\/\/|#|--|;)\s*code generated .*do not edit\.?\s*$/im,
|
|
11
|
+
/^\s*(?:\/\/|#|--|;)\s*do not edit\.?\s*$/im,
|
|
12
|
+
/^\s*(?:\/\/|#|--|;|<!--)\s*<auto-generated\b/im,
|
|
13
|
+
/^\s*(?:\/\/|#|--|;)\s*this file was generated\b/im,
|
|
14
|
+
/^\s*(?:\/\/|#|--|;)\s*generated by\b/im
|
|
15
|
+
];
|
|
16
|
+
export async function runModularityCheck(projectRoot, options) {
|
|
17
|
+
const limit = options.limit ?? 300;
|
|
18
|
+
const candidates = new Set();
|
|
19
|
+
for (const file of options.files ?? []) {
|
|
20
|
+
candidates.add(normalizeExplicitPath(projectRoot, file));
|
|
21
|
+
}
|
|
22
|
+
if (options.touched) {
|
|
23
|
+
for (const relative of await gitTouchedFiles(projectRoot)) {
|
|
24
|
+
candidates.add(normalizeGitPath(relative));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (options.base) {
|
|
28
|
+
for (const relative of await gitDiffFiles(projectRoot, options.base)) {
|
|
29
|
+
candidates.add(normalizeGitPath(relative));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const files = [];
|
|
33
|
+
for (const relativePath of [...candidates].sort()) {
|
|
34
|
+
if (!shouldIncludeCodeFile(relativePath)) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const absolutePath = path.join(projectRoot, ...relativePath.split("/"));
|
|
38
|
+
if (!(await isRegularFile(absolutePath)) || (await isLikelyGeneratedFile(absolutePath))) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const lines = countPhysicalLines(await fs.readFile(absolutePath, "utf8"));
|
|
42
|
+
files.push({ relativePath, lines, overLimit: lines > limit });
|
|
43
|
+
}
|
|
44
|
+
const warnings = files
|
|
45
|
+
.filter((file) => file.overLimit)
|
|
46
|
+
.map((file) => `${file.relativePath}: ${file.lines} physical lines exceeds limit ${limit}`);
|
|
47
|
+
return { limit, files, warnings };
|
|
48
|
+
}
|
|
49
|
+
export function countPhysicalLines(content) {
|
|
50
|
+
if (content.length === 0) {
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
const lines = content.split(/\r\n|\n|\r/);
|
|
54
|
+
if (lines[lines.length - 1] === "") {
|
|
55
|
+
lines.pop();
|
|
56
|
+
}
|
|
57
|
+
return lines.length;
|
|
58
|
+
}
|
|
59
|
+
async function gitTouchedFiles(projectRoot) {
|
|
60
|
+
const result = await execFileAsync("git", ["-C", projectRoot, "status", "--porcelain", "-z"], {
|
|
61
|
+
encoding: "utf8",
|
|
62
|
+
maxBuffer: GIT_MAX_BUFFER
|
|
63
|
+
});
|
|
64
|
+
const records = result.stdout.split("\0").filter(Boolean);
|
|
65
|
+
const files = [];
|
|
66
|
+
for (let index = 0; index < records.length; index += 1) {
|
|
67
|
+
const record = records[index];
|
|
68
|
+
const status = record.slice(0, 2);
|
|
69
|
+
const relative = record.slice(3);
|
|
70
|
+
if (relative) {
|
|
71
|
+
files.push(relative);
|
|
72
|
+
}
|
|
73
|
+
if (status.includes("R") || status.includes("C")) {
|
|
74
|
+
index += 1;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return files;
|
|
78
|
+
}
|
|
79
|
+
async function gitDiffFiles(projectRoot, base) {
|
|
80
|
+
const result = await execFileAsync("git", ["-C", projectRoot, "diff", "--name-only", "-z", base, "--"], {
|
|
81
|
+
encoding: "utf8",
|
|
82
|
+
maxBuffer: GIT_MAX_BUFFER
|
|
83
|
+
});
|
|
84
|
+
return result.stdout.split("\0").filter(Boolean);
|
|
85
|
+
}
|
|
86
|
+
function normalizeExplicitPath(projectRoot, value) {
|
|
87
|
+
const absolute = path.isAbsolute(value) ? path.resolve(value) : path.resolve(projectRoot, value);
|
|
88
|
+
const relative = toPosix(path.relative(projectRoot, absolute));
|
|
89
|
+
if (relative.startsWith("../") || relative === ".." || path.isAbsolute(relative)) {
|
|
90
|
+
throw new Error(`check-modularity --file must stay inside the project root: ${value}`);
|
|
91
|
+
}
|
|
92
|
+
return normalizeGitPath(relative);
|
|
93
|
+
}
|
|
94
|
+
function normalizeGitPath(value) {
|
|
95
|
+
return toPosix(value).replace(/^\.\//, "");
|
|
96
|
+
}
|
|
97
|
+
async function isRegularFile(target) {
|
|
98
|
+
try {
|
|
99
|
+
return (await fs.stat(target)).isFile();
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function isLikelyGeneratedFile(target) {
|
|
106
|
+
const content = await fs.readFile(target, "utf8");
|
|
107
|
+
const sample = content.slice(0, 8192);
|
|
108
|
+
return GENERATED_FILE_PATTERNS.some((pattern) => pattern.test(sample));
|
|
109
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
const EXCLUDED_DIR_NAMES = new Set([
|
|
3
|
+
".git",
|
|
4
|
+
".artifacts",
|
|
5
|
+
".cache",
|
|
6
|
+
".next",
|
|
7
|
+
".nuxt",
|
|
8
|
+
".runtime",
|
|
9
|
+
".turbo",
|
|
10
|
+
"artifacts",
|
|
11
|
+
"build",
|
|
12
|
+
"cache",
|
|
13
|
+
"captures",
|
|
14
|
+
"coverage",
|
|
15
|
+
"dist",
|
|
16
|
+
"logs",
|
|
17
|
+
"node_modules",
|
|
18
|
+
"out",
|
|
19
|
+
"playwright-report",
|
|
20
|
+
"raw-captures",
|
|
21
|
+
"reports",
|
|
22
|
+
"target",
|
|
23
|
+
"temp",
|
|
24
|
+
"test-reports",
|
|
25
|
+
"test-results",
|
|
26
|
+
"tmp"
|
|
27
|
+
]);
|
|
28
|
+
export const SAFE_EXAMPLE_FILE_NAMES = new Set([".env.example", ".env.sample", ".env.template", "example.env", "sample.env"]);
|
|
29
|
+
const EXCLUDED_FILE_PATTERNS = [
|
|
30
|
+
/^\.env(?:\.|$)/i,
|
|
31
|
+
/\.log$/i,
|
|
32
|
+
/\.min\.(?:css|js|mjs)$/i,
|
|
33
|
+
/(^|[-_.])(secret|secrets|cookie|cookies|credential|credentials|api-key|apikey|access-token|refresh-token|auth-token|private-key)([-_.]|$)/i,
|
|
34
|
+
/(^|[-_.])(raw-capture|capture-dump|licensed-payload|license-payload|test-report)([-_.]|$)/i,
|
|
35
|
+
/^(?:package-lock\.json|npm-shrinkwrap\.json|pnpm-lock\.yaml|yarn\.lock|poetry\.lock|pipfile\.lock|cargo\.lock)$/i,
|
|
36
|
+
/full-project-context-\d{8}T\d{6}Z\.md$/i,
|
|
37
|
+
/当前项目context-\d{8}T\d{6}Z\.md$/i,
|
|
38
|
+
/当前项目代码实现\.md$/i,
|
|
39
|
+
/(^|[-_.])(code-level-implementation|context-export|context-bundle)([-_.]|$)/i
|
|
40
|
+
];
|
|
41
|
+
const CODE_FILE_EXTENSIONS = [
|
|
42
|
+
".bat",
|
|
43
|
+
".cjs",
|
|
44
|
+
".cmd",
|
|
45
|
+
".go",
|
|
46
|
+
".gql",
|
|
47
|
+
".graphql",
|
|
48
|
+
".js",
|
|
49
|
+
".jsx",
|
|
50
|
+
".jsonc",
|
|
51
|
+
".mjs",
|
|
52
|
+
".proto",
|
|
53
|
+
".ps1",
|
|
54
|
+
".py",
|
|
55
|
+
".sh",
|
|
56
|
+
".sql",
|
|
57
|
+
".toml",
|
|
58
|
+
".ts",
|
|
59
|
+
".tsx",
|
|
60
|
+
".vue",
|
|
61
|
+
".yaml",
|
|
62
|
+
".yml"
|
|
63
|
+
];
|
|
64
|
+
const CODE_FILE_BASE_NAMES = new Set([
|
|
65
|
+
".env.example",
|
|
66
|
+
".env.sample",
|
|
67
|
+
".env.template",
|
|
68
|
+
"dockerfile",
|
|
69
|
+
"makefile",
|
|
70
|
+
"package.json",
|
|
71
|
+
"pyproject.toml",
|
|
72
|
+
"requirements.txt",
|
|
73
|
+
"setup.cfg",
|
|
74
|
+
"tsconfig.json"
|
|
75
|
+
]);
|
|
76
|
+
const CONFIG_JSON_NAMES = new Set([
|
|
77
|
+
"babel.config.json",
|
|
78
|
+
"biome.json",
|
|
79
|
+
"composer.json",
|
|
80
|
+
"deno.json",
|
|
81
|
+
"eslint.config.json",
|
|
82
|
+
"jsconfig.json",
|
|
83
|
+
"package.json",
|
|
84
|
+
"tsconfig.json",
|
|
85
|
+
"vite.config.json"
|
|
86
|
+
]);
|
|
87
|
+
export function shouldIncludeCodeFile(relative) {
|
|
88
|
+
if (shouldExcludeRelativePath(relative)) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
const normalized = toPosix(relative);
|
|
92
|
+
const lower = normalized.toLowerCase();
|
|
93
|
+
const base = path.posix.basename(lower);
|
|
94
|
+
if (CODE_FILE_BASE_NAMES.has(base) || base.startsWith("dockerfile.")) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
if (lower.endsWith(".dockerfile")) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
if (lower.endsWith(".json")) {
|
|
101
|
+
return isConfigJson(lower);
|
|
102
|
+
}
|
|
103
|
+
return CODE_FILE_EXTENSIONS.some((extension) => lower.endsWith(extension));
|
|
104
|
+
}
|
|
105
|
+
export function shouldExcludeRelativePath(relative) {
|
|
106
|
+
const normalized = toPosix(relative);
|
|
107
|
+
const segments = normalized.split("/");
|
|
108
|
+
if (segments.some((segment) => EXCLUDED_DIR_NAMES.has(segment))) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
const base = segments[segments.length - 1] ?? "";
|
|
112
|
+
const lowerBase = base.toLowerCase();
|
|
113
|
+
return EXCLUDED_FILE_PATTERNS.some((pattern) => {
|
|
114
|
+
if (SAFE_EXAMPLE_FILE_NAMES.has(lowerBase) && pattern.source.startsWith("^\\.env")) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
return pattern.test(base) || pattern.test(normalized);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
export function toPosix(value) {
|
|
121
|
+
return value.replace(/\\/g, "/");
|
|
122
|
+
}
|
|
123
|
+
function isConfigJson(lowerRelative) {
|
|
124
|
+
const base = path.posix.basename(lowerRelative);
|
|
125
|
+
return (CONFIG_JSON_NAMES.has(base) ||
|
|
126
|
+
lowerRelative.endsWith(".schema.json") ||
|
|
127
|
+
lowerRelative.includes("/schema/") ||
|
|
128
|
+
lowerRelative.includes("/schemas/") ||
|
|
129
|
+
lowerRelative.includes("/config/") ||
|
|
130
|
+
lowerRelative.includes("/configs/") ||
|
|
131
|
+
lowerRelative.includes("/examples/") ||
|
|
132
|
+
lowerRelative.includes("/sample/") ||
|
|
133
|
+
lowerRelative.includes("/samples/") ||
|
|
134
|
+
base.includes("config") ||
|
|
135
|
+
base.includes("schema") ||
|
|
136
|
+
base.includes("example") ||
|
|
137
|
+
base.includes("sample"));
|
|
138
|
+
}
|
package/dist/lib/upgrade.d.ts
CHANGED
|
@@ -1 +1,10 @@
|
|
|
1
|
+
export interface UpgradeRunReport {
|
|
2
|
+
lines: string[];
|
|
3
|
+
blocked: boolean;
|
|
4
|
+
}
|
|
5
|
+
export declare class UpgradeBlockedError extends Error {
|
|
6
|
+
readonly lines: string[];
|
|
7
|
+
constructor(lines: string[]);
|
|
8
|
+
}
|
|
1
9
|
export declare function runUpgrade(projectRoot: string): Promise<string[]>;
|
|
10
|
+
export declare function runUpgradeReport(projectRoot: string): Promise<UpgradeRunReport>;
|
package/dist/lib/upgrade.js
CHANGED
|
@@ -1,11 +1,33 @@
|
|
|
1
1
|
import { runDoctor } from "./doctor.js";
|
|
2
|
-
import { formatUpgradePlan, runMigrations } from "./migrations.js";
|
|
2
|
+
import { createUpgradePlan, formatUpgradePlan, runMigrations } from "./migrations.js";
|
|
3
3
|
import { assertSupportedSchema } from "./schema-guard.js";
|
|
4
4
|
import { runSync } from "./sync-engine.js";
|
|
5
|
+
export class UpgradeBlockedError extends Error {
|
|
6
|
+
lines;
|
|
7
|
+
constructor(lines) {
|
|
8
|
+
super("upgrade completed with blockers");
|
|
9
|
+
this.lines = lines;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
5
12
|
export async function runUpgrade(projectRoot) {
|
|
13
|
+
const report = await runUpgradeReport(projectRoot);
|
|
14
|
+
if (report.blocked) {
|
|
15
|
+
throw new UpgradeBlockedError(report.lines);
|
|
16
|
+
}
|
|
17
|
+
return report.lines;
|
|
18
|
+
}
|
|
19
|
+
export async function runUpgradeReport(projectRoot) {
|
|
6
20
|
const lines = [];
|
|
7
21
|
await assertSupportedSchema(projectRoot, "upgrade");
|
|
8
|
-
const
|
|
22
|
+
const plan = await createUpgradePlan(projectRoot);
|
|
23
|
+
if (plan.blocked.length > 0) {
|
|
24
|
+
lines.push(...formatUpgradePlan(plan));
|
|
25
|
+
lines.push("upgrade blocked: resolve blocked migration items before applying safe migrations or sync.");
|
|
26
|
+
const doctor = await runDoctor(projectRoot);
|
|
27
|
+
lines.push(`doctor warnings=${doctor.warnings.length} errors=${doctor.errors.length}`);
|
|
28
|
+
return { lines, blocked: true };
|
|
29
|
+
}
|
|
30
|
+
const migrationReport = await runMigrations(projectRoot, plan);
|
|
9
31
|
lines.push(`migrations changed=${migrationReport.changed.length} skipped=${migrationReport.skipped.length} manual_required=${migrationReport.manualRequired.length} blocked=${migrationReport.blocked.length}`);
|
|
10
32
|
if (migrationReport.manualRequired.length > 0 || migrationReport.blocked.length > 0) {
|
|
11
33
|
lines.push(...formatUpgradePlan({
|
|
@@ -21,11 +43,9 @@ export async function runUpgrade(projectRoot) {
|
|
|
21
43
|
}
|
|
22
44
|
const doctor = await runDoctor(projectRoot);
|
|
23
45
|
lines.push(`doctor warnings=${doctor.warnings.length} errors=${doctor.errors.length}`);
|
|
24
|
-
|
|
46
|
+
const blocked = migrationReport.manualRequired.length > 0 ||
|
|
25
47
|
migrationReport.blocked.length > 0 ||
|
|
26
48
|
syncReport.blocked.length > 0 ||
|
|
27
|
-
doctor.errors.length > 0
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
return lines;
|
|
49
|
+
doctor.errors.length > 0;
|
|
50
|
+
return { lines, blocked };
|
|
31
51
|
}
|