phasegate 0.315.0 → 0.335.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/ADR/039-hook-observable-state-as-authorization-unit.md +74 -0
- package/docs/ADR/040-quick-mode-config-via-preset-resolution.md +66 -0
- package/docs/guide/cli-reference.md +89 -7
- package/docs/guide/configuration.md +43 -2
- package/docs/guide/quick-vs-full-mode.md +36 -1
- package/package.json +2 -2
- package/scripts/harness/agent-integration/application/dto/handle-pre-tool-use-dto.ts +5 -1
- package/scripts/harness/agent-integration/application/usecases/handle-pre-tool-use-usecase.ts +16 -8
- package/scripts/harness/agent-integration/domain/services/bash-write-target-extractor.ts +190 -2
- package/scripts/harness/agent-integration/domain/value-objects/protected-file-list.ts +5 -0
- package/scripts/harness/agent-integration/infrastructure/adapters/quick-mode-full-mode-requirement-adapter.ts +15 -1
- package/scripts/harness/agent-integration/presentation/pre-tool-use-hook.ts +6 -3
- package/scripts/harness/ci-governance/application/dto/scaffold-inception-input.ts +9 -0
- package/scripts/harness/ci-governance/application/dto/scaffold-inception-output.ts +13 -0
- package/scripts/harness/ci-governance/application/usecases/list-templates-usecase.ts +30 -0
- package/scripts/harness/ci-governance/application/usecases/scaffold-inception-usecase.ts +63 -0
- package/scripts/harness/ci-governance/application/usecases/show-template-usecase.ts +39 -0
- package/scripts/harness/ci-governance/composition-root.ts +55 -3
- package/scripts/harness/ci-governance/domain/ports/inception-doc-writer-port.ts +19 -0
- package/scripts/harness/ci-governance/domain/ports/inception-template-repository-port.ts +16 -0
- package/scripts/harness/ci-governance/domain/ports/template-catalog-port.ts +23 -0
- package/scripts/harness/ci-governance/domain/value-objects/inception-doc-kind.ts +109 -0
- package/scripts/harness/ci-governance/domain/value-objects/template-catalog-entry.ts +53 -0
- package/scripts/harness/ci-governance/domain/value-objects/template-name.ts +50 -0
- package/scripts/harness/ci-governance/infrastructure/adapters/file-system-inception-doc-writer-adapter.ts +42 -0
- package/scripts/harness/ci-governance/infrastructure/adapters/file-system-inception-template-repository-adapter.ts +32 -0
- package/scripts/harness/ci-governance/infrastructure/adapters/file-system-template-catalog-adapter.ts +60 -0
- package/scripts/harness/ci-governance/presentation/handlers/scaffold-inception-handler.ts +101 -0
- package/scripts/harness/ci-governance/presentation/handlers/templates-handler.ts +103 -0
- package/scripts/harness/config-foundation/domain/harness-config.ts +12 -0
- package/scripts/harness/config-foundation/infrastructure/presets/minimal.json +2 -2
- package/scripts/harness/config-foundation/infrastructure/presets/standard.json +2 -2
- package/scripts/harness/config-foundation/infrastructure/presets/strict.json +2 -2
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +44 -1
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +42 -1
- package/scripts/harness/harness-api/domain/value-objects/known-harness-commands.ts +3 -1
- package/scripts/harness/main.ts +102 -12
- package/scripts/harness/phase-dependency-model/application/services/evidence-bundle-assembler.ts +2 -1
- package/scripts/harness/phase-dependency-model/domain/ports/plan-document-reader-port.ts +12 -0
- package/scripts/harness/phase-dependency-model/infrastructure/filesystem/markdown-plan-document-reader.ts +4 -1
- package/scripts/harness/quick-mode/domain/errors/quick-mode-config-error.ts +19 -0
- package/scripts/harness/quick-mode/domain/services/quick-mode-judgment-engine.ts +46 -17
- package/scripts/harness/quick-mode/domain/value-objects/category-override-rules.ts +147 -0
- package/scripts/harness/quick-mode/domain/value-objects/change-category.ts +33 -0
- package/scripts/harness/quick-mode/domain/value-objects/quick-mode-config.ts +31 -10
- package/scripts/harness/quick-mode/index.ts +7 -1
- package/scripts/harness/quick-mode/infrastructure/adapters/harness-config-quick-mode-config-adapter.ts +83 -32
- package/skills/product-architect/SKILL.md +19 -0
- package/skills/story-mapper/SKILL.md +11 -0
- package/skills/story-writer/SKILL.md +11 -0
- package/skills/unit-designer/SKILL.md +11 -0
- package/templates/product_overview.template.md +85 -0
- package/templates/product_overview_plan.template.md +55 -0
- package/templates/story_mapping_plan.template.md +61 -0
- package/templates/story_writer_plan.template.md +61 -0
- package/templates/unit_design_plan.template.md +63 -0
package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json
CHANGED
|
@@ -13,6 +13,18 @@
|
|
|
13
13
|
"decisionSignal": {
|
|
14
14
|
"type": "string",
|
|
15
15
|
"enum": ["business-rule-branch", "validation-rule", "error-construction", "state-transition", "policy-selection"]
|
|
16
|
+
},
|
|
17
|
+
"changeCategory": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"enum": ["bugfix", "docs", "test", "config", "feature", "domain", "api"]
|
|
20
|
+
},
|
|
21
|
+
"categoryOverrideGlobs": {
|
|
22
|
+
"type": "array",
|
|
23
|
+
"items": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"minLength": 1
|
|
26
|
+
},
|
|
27
|
+
"uniqueItems": true
|
|
16
28
|
}
|
|
17
29
|
},
|
|
18
30
|
"properties": {
|
|
@@ -170,9 +182,10 @@
|
|
|
170
182
|
"additionalProperties": false,
|
|
171
183
|
"properties": {
|
|
172
184
|
"allowedCategories": {
|
|
185
|
+
"description": "Quick Mode を通過させる ChangeCategory。7 値の enum で、未知値は設定エラーとして拒否される。",
|
|
173
186
|
"type": "array",
|
|
174
187
|
"items": {
|
|
175
|
-
"
|
|
188
|
+
"$ref": "#/$defs/changeCategory"
|
|
176
189
|
},
|
|
177
190
|
"uniqueItems": true
|
|
178
191
|
},
|
|
@@ -204,6 +217,34 @@
|
|
|
204
217
|
"type": "boolean"
|
|
205
218
|
}
|
|
206
219
|
}
|
|
220
|
+
},
|
|
221
|
+
"categoryOverrides": {
|
|
222
|
+
"description": "プロジェクト固有パスを任意の ChangeCategory へ割り当てる glob ルール。組み込み分類より先に評価されるが、組み込みが domain / api のファイルは降格できない。",
|
|
223
|
+
"type": "object",
|
|
224
|
+
"additionalProperties": false,
|
|
225
|
+
"properties": {
|
|
226
|
+
"bugfix": {
|
|
227
|
+
"$ref": "#/$defs/categoryOverrideGlobs"
|
|
228
|
+
},
|
|
229
|
+
"docs": {
|
|
230
|
+
"$ref": "#/$defs/categoryOverrideGlobs"
|
|
231
|
+
},
|
|
232
|
+
"test": {
|
|
233
|
+
"$ref": "#/$defs/categoryOverrideGlobs"
|
|
234
|
+
},
|
|
235
|
+
"config": {
|
|
236
|
+
"$ref": "#/$defs/categoryOverrideGlobs"
|
|
237
|
+
},
|
|
238
|
+
"feature": {
|
|
239
|
+
"$ref": "#/$defs/categoryOverrideGlobs"
|
|
240
|
+
},
|
|
241
|
+
"domain": {
|
|
242
|
+
"$ref": "#/$defs/categoryOverrideGlobs"
|
|
243
|
+
},
|
|
244
|
+
"api": {
|
|
245
|
+
"$ref": "#/$defs/categoryOverrideGlobs"
|
|
246
|
+
}
|
|
247
|
+
}
|
|
207
248
|
}
|
|
208
249
|
}
|
|
209
250
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @unit harness-api
|
|
2
2
|
// @layer domain
|
|
3
|
-
// @work-item-id WI-250, WI-291, WI-296
|
|
3
|
+
// @work-item-id WI-250, WI-291, WI-296, WI-367, WI-368
|
|
4
4
|
// known-harness-commands.ts — CLI 実サーフェスの canonical 既知コマンド一覧
|
|
5
5
|
//
|
|
6
6
|
// main.ts の CLI dispatch(`switch (command)`)が受理する全トップレベルコマンド名の
|
|
@@ -70,6 +70,7 @@ export const KNOWN_HARNESS_COMMANDS: readonly string[] = Object.freeze([
|
|
|
70
70
|
"regression:run-k14-k15",
|
|
71
71
|
"render-errors",
|
|
72
72
|
"scaffold-design",
|
|
73
|
+
"scaffold-inception",
|
|
73
74
|
"scaffold-wi",
|
|
74
75
|
"session",
|
|
75
76
|
"setup:agent",
|
|
@@ -80,6 +81,7 @@ export const KNOWN_HARNESS_COMMANDS: readonly string[] = Object.freeze([
|
|
|
80
81
|
"skill:validate-structure",
|
|
81
82
|
"skills",
|
|
82
83
|
"status",
|
|
84
|
+
"templates",
|
|
83
85
|
"uninstall",
|
|
84
86
|
"update-skills",
|
|
85
87
|
"validate",
|
package/scripts/harness/main.ts
CHANGED
|
@@ -45,7 +45,7 @@ import { basename, dirname, join, resolve } from "node:path";
|
|
|
45
45
|
import { fileURLToPath } from "node:url";
|
|
46
46
|
import { createAdrFoundationModule } from "./adr-foundation/composition-root.js";
|
|
47
47
|
import { createBiomeAstEngineModule } from "./biome-ast-engine/composition-root.js";
|
|
48
|
-
import { buildCiGovernance } from "./ci-governance/composition-root.js";
|
|
48
|
+
import { buildCiGovernance, type CiGovernanceDocPaths } from "./ci-governance/composition-root.js";
|
|
49
49
|
import { toPhaseConfigSection } from "./config-foundation/application/mappers/phase-config-section-mapper.js";
|
|
50
50
|
import { toValidatorSystemConfig } from "./config-foundation/application/mappers/validator-system-config-mapper.js";
|
|
51
51
|
import { toWorldModelConfig } from "./config-foundation/application/mappers/world-model-config-mapper.js";
|
|
@@ -120,6 +120,19 @@ function toTraceabilityModelOptions(resolvedConfig: HarnessConfigV2 | undefined)
|
|
|
120
120
|
return resolvedConfig ? { pathRoots: { designDocsRoot: resolvedConfig.paths.designDocs } } : undefined;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
/**
|
|
124
|
+
* WI-369: ci-governance の scaffold 系アダプタに解決済み paths を渡す。
|
|
125
|
+
* これが無いと `scaffold-design` の書き込み先が `docs/product/construction` に
|
|
126
|
+
* 固定され、`paths.designDocs` を移設した PJ で scaffold 先とゲート検査先がズレる。
|
|
127
|
+
*/
|
|
128
|
+
function toCiGovernanceDocPaths(resolvedConfig: HarnessConfigV2 | undefined): CiGovernanceDocPaths {
|
|
129
|
+
if (!resolvedConfig) return {};
|
|
130
|
+
return {
|
|
131
|
+
designDocs: resolvedConfig.paths.designDocs,
|
|
132
|
+
inceptionDocs: resolvedConfig.paths.inceptionDocs,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
123
136
|
interface PackageJsonDocument {
|
|
124
137
|
readonly [key: string]: unknown;
|
|
125
138
|
readonly dependencies?: unknown;
|
|
@@ -263,6 +276,7 @@ Gate semantics:
|
|
|
263
276
|
ci:check-repetition Check error repetition (--code <errorCode>, --reset, --json)
|
|
264
277
|
baseline Create retrofit baseline snapshot (--dry-run, --force, --paths <glob,glob,...>, --json)
|
|
265
278
|
scaffold-design Scaffold a design doc (--unit <id>, --phase <logical|domain|uiux|unit-test|it-test>, --dry-run|--apply, --force, --json)
|
|
279
|
+
scaffold-inception Scaffold an inception/product doc (--kind <product-overview-plan|product-overview|story-writer-plan|story-mapping-plan|unit-design-plan>, --dry-run|--apply, --force, --json)
|
|
266
280
|
session begin Start a Full Mode session (--mode full, --unit, --work-item, --reason, --duration)
|
|
267
281
|
session end End a Full Mode session (--work-item)
|
|
268
282
|
|
|
@@ -294,6 +308,10 @@ Skills:
|
|
|
294
308
|
skills list List all available skills
|
|
295
309
|
skills info <name> Show skill details (SKILL.md)
|
|
296
310
|
|
|
311
|
+
Templates:
|
|
312
|
+
templates list List bundled document templates
|
|
313
|
+
templates show <name> Print a bundled template to stdout
|
|
314
|
+
|
|
297
315
|
Options:
|
|
298
316
|
--help Show this help message
|
|
299
317
|
--version Show version number
|
|
@@ -900,6 +918,33 @@ Options:
|
|
|
900
918
|
--force Overwrite an existing target when applying.
|
|
901
919
|
--json Output machine-readable JSON.
|
|
902
920
|
--help, -h Show this help`,
|
|
921
|
+
"scaffold-inception": `Usage: phasegate scaffold-inception --kind <doc-kind> [options]
|
|
922
|
+
|
|
923
|
+
Scaffold an inception plan document or the product overview.
|
|
924
|
+
Targets follow paths.inceptionDocs / paths.designDocs from phasegate.config.json.
|
|
925
|
+
|
|
926
|
+
Options:
|
|
927
|
+
--kind <doc-kind> product-overview-plan, product-overview, story-writer-plan,
|
|
928
|
+
story-mapping-plan, or unit-design-plan.
|
|
929
|
+
--dry-run Preview target and template without writing (default).
|
|
930
|
+
--apply Write the scaffold.
|
|
931
|
+
--force Overwrite an existing target when applying.
|
|
932
|
+
--json Output machine-readable JSON.
|
|
933
|
+
--help, -h Show this help`,
|
|
934
|
+
templates: `Usage: phasegate templates <list|show <name>>
|
|
935
|
+
|
|
936
|
+
Print bundled document templates to stdout, without reading node_modules.
|
|
937
|
+
|
|
938
|
+
Subcommands:
|
|
939
|
+
list List available template names.
|
|
940
|
+
show <name> Print the template body to stdout.
|
|
941
|
+
|
|
942
|
+
Options:
|
|
943
|
+
--json Output machine-readable JSON (list only).
|
|
944
|
+
--help, -h Show this help
|
|
945
|
+
|
|
946
|
+
Notes:
|
|
947
|
+
<name> is matched against the bundled template catalog. Paths are rejected.`,
|
|
903
948
|
session: `Usage: phasegate session <begin|end> [options]
|
|
904
949
|
|
|
905
950
|
Manage hook-visible Full Mode sessions.
|
|
@@ -2965,7 +3010,10 @@ async function main(): Promise<void> {
|
|
|
2965
3010
|
case "ci-check": {
|
|
2966
3011
|
const quick = hasFlag(args, "--quick");
|
|
2967
3012
|
if (quick) {
|
|
2968
|
-
|
|
3013
|
+
// WI-364: check-change-category (WI-351) と同様に解決済み configPath / rootDir を注入する。
|
|
3014
|
+
// 無指定だと quickMode 設定を cwd 基準でしか探せず、サブディレクトリ実行で
|
|
3015
|
+
// HarnessConfigNotFoundError → exit 2 になっていた。
|
|
3016
|
+
const mod = createQuickModeCompositionRoot(await resolveQuickModeCompositionOptions());
|
|
2969
3017
|
const failOnReject = hasFlag(args, "--fail-on-reject");
|
|
2970
3018
|
const dryRun = hasFlag(args, "--dry-run");
|
|
2971
3019
|
const files = parseFlag(args, "--files");
|
|
@@ -3241,7 +3289,7 @@ Examples:
|
|
|
3241
3289
|
console.error(flagError);
|
|
3242
3290
|
process.exit(2);
|
|
3243
3291
|
}
|
|
3244
|
-
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
3292
|
+
const mod = buildCiGovernance(rootDir, harnessRoot, toCiGovernanceDocPaths(resolvedConfig));
|
|
3245
3293
|
const presetId = parseFlag(args, "--preset") ?? "standard";
|
|
3246
3294
|
const templateType = parseFlag(args, "--type") ?? "aidlc-gate";
|
|
3247
3295
|
const render = hasFlag(args, "--render");
|
|
@@ -3253,7 +3301,7 @@ Examples:
|
|
|
3253
3301
|
}
|
|
3254
3302
|
|
|
3255
3303
|
case "ci:migrate-agents-md": {
|
|
3256
|
-
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
3304
|
+
const mod = buildCiGovernance(rootDir, harnessRoot, toCiGovernanceDocPaths(resolvedConfig));
|
|
3257
3305
|
const dryRun = hasFlag(args, "--dry-run");
|
|
3258
3306
|
const validateOnly = hasFlag(args, "--validate-only");
|
|
3259
3307
|
const format = json ? "json" : "human";
|
|
@@ -3264,7 +3312,7 @@ Examples:
|
|
|
3264
3312
|
}
|
|
3265
3313
|
|
|
3266
3314
|
case "ci:auto-refresh-agent-context": {
|
|
3267
|
-
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
3315
|
+
const mod = buildCiGovernance(rootDir, harnessRoot, toCiGovernanceDocPaths(resolvedConfig));
|
|
3268
3316
|
const dryRun = hasFlag(args, "--dry-run");
|
|
3269
3317
|
const apply = hasFlag(args, "--apply");
|
|
3270
3318
|
const format = json ? "json" : "human";
|
|
@@ -3275,7 +3323,7 @@ Examples:
|
|
|
3275
3323
|
}
|
|
3276
3324
|
|
|
3277
3325
|
case "refresh-claude-md": {
|
|
3278
|
-
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
3326
|
+
const mod = buildCiGovernance(rootDir, harnessRoot, toCiGovernanceDocPaths(resolvedConfig));
|
|
3279
3327
|
const dryRun = hasFlag(args, "--dry-run");
|
|
3280
3328
|
const apply = hasFlag(args, "--apply");
|
|
3281
3329
|
const format = json ? "json" : "human";
|
|
@@ -3286,7 +3334,7 @@ Examples:
|
|
|
3286
3334
|
}
|
|
3287
3335
|
|
|
3288
3336
|
case "p2:check-agent-context": {
|
|
3289
|
-
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
3337
|
+
const mod = buildCiGovernance(rootDir, harnessRoot, toCiGovernanceDocPaths(resolvedConfig));
|
|
3290
3338
|
const thresholdRaw = parseFlag(args, "--threshold-days");
|
|
3291
3339
|
const thresholdDays = thresholdRaw === undefined ? undefined : Number(thresholdRaw);
|
|
3292
3340
|
const format = json ? "json" : "human";
|
|
@@ -3297,7 +3345,7 @@ Examples:
|
|
|
3297
3345
|
}
|
|
3298
3346
|
|
|
3299
3347
|
case "ci:check-repetition": {
|
|
3300
|
-
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
3348
|
+
const mod = buildCiGovernance(rootDir, harnessRoot, toCiGovernanceDocPaths(resolvedConfig));
|
|
3301
3349
|
const errorCode = parseFlag(args, "--code") ?? "";
|
|
3302
3350
|
const reset = hasFlag(args, "--reset");
|
|
3303
3351
|
const format = json ? "json" : "human";
|
|
@@ -3308,7 +3356,7 @@ Examples:
|
|
|
3308
3356
|
}
|
|
3309
3357
|
|
|
3310
3358
|
case "baseline": {
|
|
3311
|
-
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
3359
|
+
const mod = buildCiGovernance(rootDir, harnessRoot, toCiGovernanceDocPaths(resolvedConfig));
|
|
3312
3360
|
const dryRun = hasFlag(args, "--dry-run");
|
|
3313
3361
|
const force = hasFlag(args, "--force");
|
|
3314
3362
|
const pathsFlag = parseFlag(args, "--paths");
|
|
@@ -3331,7 +3379,7 @@ Examples:
|
|
|
3331
3379
|
}
|
|
3332
3380
|
|
|
3333
3381
|
case "scaffold-design": {
|
|
3334
|
-
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
3382
|
+
const mod = buildCiGovernance(rootDir, harnessRoot, toCiGovernanceDocPaths(resolvedConfig));
|
|
3335
3383
|
const unit = parseFlag(args, "--unit") ?? "";
|
|
3336
3384
|
const phase = parseFlag(args, "--phase") ?? "";
|
|
3337
3385
|
const dryRun = hasFlag(args, "--dry-run");
|
|
@@ -3351,8 +3399,50 @@ Examples:
|
|
|
3351
3399
|
return;
|
|
3352
3400
|
}
|
|
3353
3401
|
|
|
3402
|
+
case "scaffold-inception": {
|
|
3403
|
+
const mod = buildCiGovernance(rootDir, harnessRoot, toCiGovernanceDocPaths(resolvedConfig));
|
|
3404
|
+
const kind = parseFlag(args, "--kind") ?? "";
|
|
3405
|
+
const dryRun = hasFlag(args, "--dry-run");
|
|
3406
|
+
const apply = hasFlag(args, "--apply");
|
|
3407
|
+
const force = hasFlag(args, "--force");
|
|
3408
|
+
const format = json ? "json" : "human";
|
|
3409
|
+
const result = await mod.scaffoldInceptionHandler.handle({
|
|
3410
|
+
kind,
|
|
3411
|
+
dryRun,
|
|
3412
|
+
apply,
|
|
3413
|
+
force,
|
|
3414
|
+
format,
|
|
3415
|
+
});
|
|
3416
|
+
console.log(result.output);
|
|
3417
|
+
await finishCliExit(result.exitCode);
|
|
3418
|
+
return;
|
|
3419
|
+
}
|
|
3420
|
+
|
|
3421
|
+
case "templates": {
|
|
3422
|
+
const mod = buildCiGovernance(rootDir, harnessRoot, toCiGovernanceDocPaths(resolvedConfig));
|
|
3423
|
+
const subCommand = args[1];
|
|
3424
|
+
const emit = async (result: { exitCode: number; output: string; errorOutput: string }): Promise<void> => {
|
|
3425
|
+
if (result.output.length > 0) console.log(result.output);
|
|
3426
|
+
if (result.errorOutput.length > 0) console.error(result.errorOutput);
|
|
3427
|
+
await finishCliExit(result.exitCode);
|
|
3428
|
+
};
|
|
3429
|
+
|
|
3430
|
+
if (subCommand === "list") {
|
|
3431
|
+
await emit(await mod.templatesHandler.list({ format: json ? "json" : "human" }));
|
|
3432
|
+
return;
|
|
3433
|
+
}
|
|
3434
|
+
|
|
3435
|
+
if (subCommand === "show") {
|
|
3436
|
+
await emit(await mod.templatesHandler.show({ name: args[2] }));
|
|
3437
|
+
return;
|
|
3438
|
+
}
|
|
3439
|
+
|
|
3440
|
+
await emit(mod.templatesHandler.usage());
|
|
3441
|
+
return;
|
|
3442
|
+
}
|
|
3443
|
+
|
|
3354
3444
|
case "integrity:pin": {
|
|
3355
|
-
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
3445
|
+
const mod = buildCiGovernance(rootDir, harnessRoot, toCiGovernanceDocPaths(resolvedConfig));
|
|
3356
3446
|
const dryRun = hasFlag(args, "--dry-run");
|
|
3357
3447
|
const format = json ? "json" : "human";
|
|
3358
3448
|
const result = await mod.integrityHandler.pin({ dryRun, format });
|
|
@@ -3362,7 +3452,7 @@ Examples:
|
|
|
3362
3452
|
}
|
|
3363
3453
|
|
|
3364
3454
|
case "integrity:verify": {
|
|
3365
|
-
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
3455
|
+
const mod = buildCiGovernance(rootDir, harnessRoot, toCiGovernanceDocPaths(resolvedConfig));
|
|
3366
3456
|
const format = json ? "json" : "human";
|
|
3367
3457
|
const result = await mod.integrityHandler.verify({ format });
|
|
3368
3458
|
console.log(result.output);
|
package/scripts/harness/phase-dependency-model/application/services/evidence-bundle-assembler.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @layer application
|
|
3
3
|
* @unit phase-dependency-model
|
|
4
|
+
* @work-item-id WI-369
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import type { ArtifactExistenceCheckerPort } from '../../domain/ports/artifact-existence-checker-port.js';
|
|
@@ -68,7 +69,7 @@ export class EvidenceBundleAssembler {
|
|
|
68
69
|
continue;
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
const actual = await this.planDocumentReader.readEvidence(node, scope, planningMode);
|
|
72
|
+
const actual = await this.planDocumentReader.readEvidence(node, scope, planningMode, pathRoots);
|
|
72
73
|
planEvidences.set(node.nodeKey(), actual);
|
|
73
74
|
}
|
|
74
75
|
|
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @layer domain
|
|
3
3
|
* @unit phase-dependency-model
|
|
4
|
+
* @work-item-id WI-369
|
|
4
5
|
*/
|
|
5
6
|
|
|
7
|
+
import type { PathRoots } from '../values/artifact.js';
|
|
6
8
|
import type { PhaseNode } from '../values/phase-node.js';
|
|
7
9
|
import type { PlanEvidence } from '../values/plan-evidence.js';
|
|
8
10
|
import type { PlanningMode } from '../values/planning-mode.js';
|
|
9
11
|
|
|
10
12
|
export interface PlanDocumentReaderPort {
|
|
13
|
+
/**
|
|
14
|
+
* plan 文書の存在と QA evidence を読む。
|
|
15
|
+
*
|
|
16
|
+
* `pathRoots` は `phasegate.config.json` の `paths` を解決したもの。
|
|
17
|
+
* 省略時は既定ルート(`docs/inception` / `docs/product/construction`)を使う。
|
|
18
|
+
* WI-369: これを渡さないと `paths.inceptionDocs` を移設した PJ で
|
|
19
|
+
* plan 文書だけが既定パスから探され、成果物検査と plan evidence の
|
|
20
|
+
* 探索先がズレる(成果物は在るのに「plan文書が不足」で落ちる)。
|
|
21
|
+
*/
|
|
11
22
|
readEvidence(
|
|
12
23
|
node: PhaseNode,
|
|
13
24
|
scope: { unitId?: string; storyId?: string },
|
|
14
25
|
expectedMode: PlanningMode,
|
|
26
|
+
pathRoots?: PathRoots,
|
|
15
27
|
): Promise<PlanEvidence>;
|
|
16
28
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @layer infrastructure
|
|
3
3
|
* @unit phase-dependency-model
|
|
4
|
+
* @work-item-id WI-369
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import { readFile } from "node:fs/promises";
|
|
7
8
|
import * as path from "node:path";
|
|
8
9
|
import type { PlanDocumentReaderPort } from "../../domain/ports/plan-document-reader-port.js";
|
|
10
|
+
import { DEFAULT_PATH_ROOTS, type PathRoots } from "../../domain/values/artifact.js";
|
|
9
11
|
import type { PhaseNode } from "../../domain/values/phase-node.js";
|
|
10
12
|
import { PlanEvidence } from "../../domain/values/plan-evidence.js";
|
|
11
13
|
import type { PlanningMode } from "../../domain/values/planning-mode.js";
|
|
@@ -41,6 +43,7 @@ export class MarkdownPlanDocumentReader implements PlanDocumentReaderPort {
|
|
|
41
43
|
node: PhaseNode,
|
|
42
44
|
scope: { unitId?: string; storyId?: string },
|
|
43
45
|
expectedMode: PlanningMode,
|
|
46
|
+
pathRoots: PathRoots = DEFAULT_PATH_ROOTS,
|
|
44
47
|
): Promise<PlanEvidence> {
|
|
45
48
|
const planArtifacts = node.planArtifacts();
|
|
46
49
|
|
|
@@ -55,7 +58,7 @@ export class MarkdownPlanDocumentReader implements PlanDocumentReaderPort {
|
|
|
55
58
|
const artifact = planArtifacts[0];
|
|
56
59
|
let resolvedPath: string;
|
|
57
60
|
try {
|
|
58
|
-
resolvedPath = artifact.resolve(scope);
|
|
61
|
+
resolvedPath = artifact.resolve(scope, pathRoots);
|
|
59
62
|
} catch {
|
|
60
63
|
return PlanEvidence.create({
|
|
61
64
|
exists: false,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @layer domain
|
|
3
|
+
* @unit quick-mode
|
|
4
|
+
* @work-item-id WI-372
|
|
5
|
+
*
|
|
6
|
+
* QuickMode 設定の不変条件違反を表すエラー。
|
|
7
|
+
*
|
|
8
|
+
* `quick-mode-config.ts` と `category-override-rules.ts` の双方から参照されるため、
|
|
9
|
+
* 循環 import を避ける目的で独立ファイルに切り出している。
|
|
10
|
+
* 後方互換のため `quick-mode-config.ts` からも re-export される。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export class QuickModeConfigError extends Error {
|
|
14
|
+
constructor(message: string) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'QuickModeConfigError';
|
|
17
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
* @work-item-id WI-204
|
|
5
5
|
* @work-item-id WI-349
|
|
6
6
|
* @work-item-id WI-352
|
|
7
|
+
* @work-item-id WI-372
|
|
7
8
|
*
|
|
8
9
|
* ChangedFile[]をChangeClassificationに変換し、3拒否ルールを評価してQuickModeEligibilityを返すドメインサービス
|
|
9
10
|
*/
|
|
10
11
|
|
|
12
|
+
import { CategoryOverrideRules } from "../value-objects/category-override-rules.js";
|
|
11
13
|
import { ChangeCategory } from "../value-objects/change-category.js";
|
|
12
14
|
import { ChangeClassification } from "../value-objects/change-classification.js";
|
|
13
15
|
import type { ChangedFile } from "../value-objects/changed-file.js";
|
|
@@ -15,17 +17,6 @@ import type { QuickModeConfig } from "../value-objects/quick-mode-config.js";
|
|
|
15
17
|
import { QuickModeEligibility } from "../value-objects/quick-mode-eligibility.js";
|
|
16
18
|
import { isCommentOnlyDiff } from "./comment-only-diff-detector.js";
|
|
17
19
|
|
|
18
|
-
// リスク順優先度(api > domain > feature > bugfix > test > config > docs)
|
|
19
|
-
const RISK_PRIORITY: Record<string, number> = {
|
|
20
|
-
api: 6,
|
|
21
|
-
domain: 5,
|
|
22
|
-
feature: 4,
|
|
23
|
-
bugfix: 3,
|
|
24
|
-
test: 2,
|
|
25
|
-
config: 1,
|
|
26
|
-
docs: 0,
|
|
27
|
-
};
|
|
28
|
-
|
|
29
20
|
// config: リポジトリ直下の bootstrap 設定ファイル(列挙型 allowlist)。
|
|
30
21
|
// これらは unit を持たず、CREATE だと feature に落ちて allowedCategories に
|
|
31
22
|
// 入れる手段がないため greenfield な初期セットアップが恒久的にブロックされていた。
|
|
@@ -57,7 +48,7 @@ function isRootBootstrapConfigFile(filePath: string): boolean {
|
|
|
57
48
|
return filePath.startsWith(HUSKY_DIRECTORY_PREFIX) && filePath.slice(HUSKY_DIRECTORY_PREFIX.length).length > 0;
|
|
58
49
|
}
|
|
59
50
|
|
|
60
|
-
function
|
|
51
|
+
function categorizeFileByBuiltInRules(file: ChangedFile): ChangeCategory {
|
|
61
52
|
const { filePath, changeKind } = file;
|
|
62
53
|
|
|
63
54
|
// Config files must stay config even when Edit payload snippets look like
|
|
@@ -130,6 +121,43 @@ function categorizeFile(file: ChangedFile): ChangeCategory {
|
|
|
130
121
|
return ChangeCategory.fromString("bugfix");
|
|
131
122
|
}
|
|
132
123
|
|
|
124
|
+
// 組み込み判定が構造的に高リスク(domain / api)のカテゴリは override で降格できない。
|
|
125
|
+
// @work-item-id WI-372
|
|
126
|
+
const NON_DOWNGRADABLE_BUILT_IN_CATEGORIES: ReadonlySet<string> = new Set(["domain", "api"]);
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* WI-372: `quickMode.categoryOverrides` を反映したカテゴリ判定。
|
|
130
|
+
*
|
|
131
|
+
* override は「このパスはこの種類の変更である」という利用者の明示宣言なので、
|
|
132
|
+
* 組み込みルールより **先** に評価する(DD-1)。組み込みの後段に置くと
|
|
133
|
+
* `notes/x.config.json` のような偶発的な組み込みマッチに常に負け、
|
|
134
|
+
* 「設定したのに効かない」不可解な挙動になるため。
|
|
135
|
+
*
|
|
136
|
+
* ただし組み込み判定が `domain` / `api` のファイルは override で降格できない(DD-2)。
|
|
137
|
+
* `judge()` の NEW_DOMAIN は CREATE のみ、API_CONTRACT は port/adapter のみを見るため、
|
|
138
|
+
* ガードが無いと domain ファイルの MODIFY が `docs` として素通りしてしまう。
|
|
139
|
+
* 降格は禁じるが昇格(domain → api 等)は許す。
|
|
140
|
+
*
|
|
141
|
+
* override 未設定時は組み込み分類と完全に一致する(後方互換)。
|
|
142
|
+
*/
|
|
143
|
+
function categorizeFile(file: ChangedFile, overrides: CategoryOverrideRules): ChangeCategory {
|
|
144
|
+
const builtIn = categorizeFileByBuiltInRules(file);
|
|
145
|
+
if (overrides.isEmpty()) {
|
|
146
|
+
return builtIn;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const override = overrides.resolve(file.filePath);
|
|
150
|
+
if (override === null) {
|
|
151
|
+
return builtIn;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (NON_DOWNGRADABLE_BUILT_IN_CATEGORIES.has(builtIn.toString())) {
|
|
155
|
+
return override.riskPriority() > builtIn.riskPriority() ? override : builtIn;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return override;
|
|
159
|
+
}
|
|
160
|
+
|
|
133
161
|
/**
|
|
134
162
|
* WI-349: 遮断理由に判定根拠(分類カテゴリと変更種別)を含める。
|
|
135
163
|
*
|
|
@@ -142,17 +170,18 @@ function describeChangedFile(file: ChangedFile, category: string): string {
|
|
|
142
170
|
}
|
|
143
171
|
|
|
144
172
|
export class QuickModeJudgmentEngine {
|
|
145
|
-
classify(changedFiles: readonly ChangedFile[],
|
|
173
|
+
classify(changedFiles: readonly ChangedFile[], config?: QuickModeConfig): ChangeClassification {
|
|
146
174
|
if (changedFiles.length === 0) {
|
|
147
175
|
return new ChangeClassification(null, new Map(), 0);
|
|
148
176
|
}
|
|
149
177
|
|
|
178
|
+
const overrides = config?.categoryOverrides ?? CategoryOverrideRules.empty();
|
|
150
179
|
const categorizedMap = new Map<string, ChangedFile[]>();
|
|
151
180
|
let dominantCategory: ChangeCategory | null = null;
|
|
152
181
|
let dominantPriority = -1;
|
|
153
182
|
|
|
154
183
|
for (const file of changedFiles) {
|
|
155
|
-
const category = categorizeFile(file);
|
|
184
|
+
const category = categorizeFile(file, overrides);
|
|
156
185
|
const key = category.toString();
|
|
157
186
|
|
|
158
187
|
if (!categorizedMap.has(key)) {
|
|
@@ -160,7 +189,7 @@ export class QuickModeJudgmentEngine {
|
|
|
160
189
|
}
|
|
161
190
|
categorizedMap.get(key)!.push(file);
|
|
162
191
|
|
|
163
|
-
const priority =
|
|
192
|
+
const priority = category.riskPriority();
|
|
164
193
|
if (priority > dominantPriority) {
|
|
165
194
|
dominantPriority = priority;
|
|
166
195
|
dominantCategory = category;
|
|
@@ -206,7 +235,7 @@ export class QuickModeJudgmentEngine {
|
|
|
206
235
|
"NEW_DOMAIN",
|
|
207
236
|
newDomainFiles,
|
|
208
237
|
`domain/配下に新規ファイルが追加されています: ${newDomainFiles
|
|
209
|
-
.map((f) => describeChangedFile(f, categorizeFile(f).toString()))
|
|
238
|
+
.map((f) => describeChangedFile(f, categorizeFile(f, config.categoryOverrides).toString()))
|
|
210
239
|
.join(", ")}`,
|
|
211
240
|
);
|
|
212
241
|
}
|
|
@@ -223,7 +252,7 @@ export class QuickModeJudgmentEngine {
|
|
|
223
252
|
"API_CONTRACT",
|
|
224
253
|
apiContractFiles,
|
|
225
254
|
`Port/Adapterインターフェースファイルの変更が含まれています: ${apiContractFiles
|
|
226
|
-
.map((f) => describeChangedFile(f, categorizeFile(f).toString()))
|
|
255
|
+
.map((f) => describeChangedFile(f, categorizeFile(f, config.categoryOverrides).toString()))
|
|
227
256
|
.join(", ")}`,
|
|
228
257
|
);
|
|
229
258
|
}
|