phasegate 0.283.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/CHANGELOG.md +16 -0
- package/docs/ADR/017-warning-severity-aggregation.md +17 -0
- package/docs/ADR/038-config-state-operation-permission-policy.md +78 -0
- 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/layer-model.md +2 -0
- package/docs/guide/quick-vs-full-mode.md +63 -3
- package/docs/guide/troubleshooting.md +37 -0
- package/docs/templates/agent-context/CLAUDE.md.template.md +6 -6
- 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 +108 -20
- package/scripts/harness/agent-integration/domain/services/bash-write-target-extractor.ts +215 -5
- package/scripts/harness/agent-integration/domain/value-objects/protected-file-list.ts +5 -0
- package/scripts/harness/agent-integration/infrastructure/adapters/file-system-full-mode-session-query-adapter.ts +75 -23
- package/scripts/harness/agent-integration/infrastructure/adapters/harness-config-config-query-adapter.ts +18 -3
- 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 +57 -9
- 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 +57 -5
- 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/services/claude-md-composer.ts +20 -11
- 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/check-repetition-handler.ts +10 -2
- 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 +3 -3
- package/scripts/harness/config-foundation/infrastructure/presets/standard.json +3 -3
- package/scripts/harness/config-foundation/infrastructure/presets/strict.json +3 -3
- 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/services/command-dispatch-service.ts +12 -3
- package/scripts/harness/harness-api/domain/value-objects/ci-check-result.ts +2 -22
- package/scripts/harness/harness-api/domain/value-objects/known-harness-commands.ts +3 -1
- package/scripts/harness/harness-error/application/dto/create-harness-error-input.ts +3 -1
- package/scripts/harness/harness-error/application/dto/harness-error-contract.ts +3 -1
- package/scripts/harness/harness-error/application/mappers/harness-error-contract-mapper.ts +9 -17
- package/scripts/harness/harness-error/application/usecases/create-harness-error-use-case.ts +7 -7
- package/scripts/harness/harness-error/domain/services/harness-error-factory.ts +30 -33
- package/scripts/harness/harness-error/domain/value-objects/error-definition.ts +28 -17
- package/scripts/harness/harness-error/domain/value-objects/harness-error.ts +30 -9
- package/scripts/harness/harness-error/domain/value-objects/remediation-type.ts +30 -0
- package/scripts/harness/harness-error/infrastructure/registry/l2-error-definitions.ts +44 -29
- package/scripts/harness/harness-error/infrastructure/registry/l3-error-definitions.ts +44 -27
- package/scripts/harness/harness-error/infrastructure/registry/l4-error-definitions.ts +47 -32
- package/scripts/harness/installation/application/checks/claude-context-missing-check.ts +13 -7
- package/scripts/harness/installation/application/checks/config-status-check.ts +52 -0
- package/scripts/harness/installation/application/checks/husky-pre-commit-missing-check.ts +6 -0
- package/scripts/harness/installation/application/ports/config-status-probe-port.ts +9 -0
- package/scripts/harness/installation/application/usecases/run-doctor-diagnostics.ts +30 -8
- package/scripts/harness/installation/application/usecases/run-install.ts +44 -11
- package/scripts/harness/installation/application/usecases/run-reconcile.ts +44 -11
- package/scripts/harness/installation/composition-root.ts +13 -3
- package/scripts/harness/installation/domain/check-id.ts +2 -0
- package/scripts/harness/installation/domain/config-status.ts +17 -0
- package/scripts/harness/installation/domain/ports/heuristic-check.ts +10 -1
- package/scripts/harness/installation/infrastructure/adapters/config-status-probe-adapter.ts +79 -0
- package/scripts/harness/installation/presentation/cli/doctor-handler.ts +6 -1
- package/scripts/harness/installation/presentation/formatters/diagnostic-report-formatter.ts +19 -5
- package/scripts/harness/integrations/pre-commit.ts +17 -3
- package/scripts/harness/main.ts +159 -21
- 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 +64 -33
- package/scripts/harness/phase2-extensions/presentation/handlers/check-freshness-handler.ts +17 -9
- package/scripts/harness/quick-mode/application/ports/file-existence-port.ts +15 -0
- package/scripts/harness/quick-mode/application/usecases/classify-change-category-usecase.ts +60 -22
- package/scripts/harness/quick-mode/composition-root.ts +25 -15
- 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 +116 -18
- 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/fs-file-existence-adapter.ts +38 -0
- package/scripts/harness/quick-mode/infrastructure/adapters/harness-config-quick-mode-config-adapter.ts +83 -32
- package/scripts/harness/skill-quality/infrastructure/adapters/file-system-requirement-test-matrix-adapter.ts +51 -8
- package/scripts/harness/skill-quality/presentation/handlers/check-coverage-handler.ts +13 -6
- package/scripts/harness/traceability-model/domain/value-objects/work-item-frontmatter.ts +5 -1
- package/scripts/harness/traceability-model/infrastructure/gateways/file-system-work-item-identity-gateway.ts +6 -1
- package/scripts/harness/traceability-model/infrastructure/gateways/file-system-work-item-status-gateway.ts +15 -2
- package/scripts/harness/validator-system/application/use-cases/aggregate-validation-results-usecase.ts +11 -14
- package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +8 -0
- package/scripts/harness/validator-system/domain/services/effective-severity-policy.ts +39 -0
- package/scripts/harness/validator-system/domain/value-objects/consistency-report.ts +6 -4
- package/scripts/harness/validator-system/domain/value-objects/drift-report.ts +12 -7
- package/scripts/harness/validator-system/domain/value-objects/validation-result.ts +11 -3
- package/scripts/harness/validator-system/infrastructure/adapters/file-system-security-pattern-scanner-adapter.ts +20 -17
- package/scripts/harness/validator-system/infrastructure/adapters/phase-dependency-phase-gate-policy-adapter.ts +35 -14
- package/scripts/harness/validator-system/infrastructure/adapters/traceability-metadata-policy-adapter.ts +13 -8
- package/scripts/harness/validator-system/presentation/formatters/agent-validation-result-formatter.ts +19 -10
- package/skills/product-architect/SKILL.md +19 -0
- package/skills/quick-implementor/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
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* @work-item-id WI-109
|
|
6
6
|
* @work-item-id WI-189
|
|
7
7
|
* @work-item-id WI-305
|
|
8
|
+
* @work-item-id WI-332
|
|
8
9
|
*
|
|
9
10
|
* Pre-commit CLI entry.
|
|
10
11
|
* Runs L2 validators against staged TypeScript files AND design-document
|
|
@@ -27,6 +28,7 @@ import type { ValidateMetadataCommandOutput } from "../traceability-model/presen
|
|
|
27
28
|
import type { AggregatedValidationReport } from "../validator-system/application/dto/aggregated-validation-report.js";
|
|
28
29
|
import type { ValidationResultContract } from "../validator-system/application/dto/validation-result-contract.js";
|
|
29
30
|
import { createValidatorSystemModule } from "../validator-system/composition-root.js";
|
|
31
|
+
import { isEffectivelyPassed } from "../validator-system/domain/services/effective-severity-policy.js";
|
|
30
32
|
import { HumanValidationResultFormatter } from "../validator-system/presentation/formatters/human-validation-result-formatter.js";
|
|
31
33
|
import { createWorldModelModule } from "../world-model/index.js";
|
|
32
34
|
|
|
@@ -293,9 +295,19 @@ function getStagedDesignChangeFiles(): string[] {
|
|
|
293
295
|
}
|
|
294
296
|
}
|
|
295
297
|
|
|
298
|
+
/**
|
|
299
|
+
* WI-332 / ADR-017: pre-commit 経路には config の failOnWarning 配線が存在しないため、
|
|
300
|
+
* ADR-017 の既定値 (false) に固定する。validate.failOnWarning を pre-commit にも効かせる
|
|
301
|
+
* 判断をする場合は、この定数を options 経由の配線に置き換えること。
|
|
302
|
+
*/
|
|
303
|
+
const PRE_COMMIT_FAIL_ON_WARNING = false;
|
|
304
|
+
|
|
296
305
|
function buildReport(results: readonly ValidationResultContract[]): AggregatedValidationReport {
|
|
297
|
-
|
|
298
|
-
|
|
306
|
+
// WI-332 / ADR-017: 手動集約 (`failed === 0` の raw passed 判定) をやめ、
|
|
307
|
+
// validate / ci-check / complete-check と同じ共有実効判定 isEffectivelyPassed を通す。
|
|
308
|
+
// warning-only の validator failure は既定で effectively passed = exit 0。
|
|
309
|
+
const passed = results.filter((r) => !r.skipped && isEffectivelyPassed(r, PRE_COMMIT_FAIL_ON_WARNING)).length;
|
|
310
|
+
const failed = results.filter((r) => !r.skipped && !isEffectivelyPassed(r, PRE_COMMIT_FAIL_ON_WARNING)).length;
|
|
299
311
|
const skipped = results.filter((r) => r.skipped).length;
|
|
300
312
|
const allErrors = results.flatMap((r) => r.errors);
|
|
301
313
|
const errorCount = allErrors.filter((e) => e.severity === "error").length;
|
|
@@ -452,7 +464,9 @@ export async function validateBypassTrailers(
|
|
|
452
464
|
}
|
|
453
465
|
|
|
454
466
|
function classifyValidatorFailure(result: ValidationResultContract): BypassBlockerClass | undefined {
|
|
455
|
-
|
|
467
|
+
// WI-332: blocker 分類も buildReport と同じ実効判定を通す。warning-only failure は
|
|
468
|
+
// exit 0 (effectively passed) なので、bypass audit の blocker としても数えない。
|
|
469
|
+
if (isEffectivelyPassed(result, PRE_COMMIT_FAIL_ON_WARNING)) return undefined;
|
|
456
470
|
const nonBypassable = NON_BYPASSABLE_VALIDATOR_IDS.includes(result.validatorId);
|
|
457
471
|
return {
|
|
458
472
|
code: result.validatorId,
|
package/scripts/harness/main.ts
CHANGED
|
@@ -41,11 +41,11 @@ import {
|
|
|
41
41
|
rm as fsRm,
|
|
42
42
|
writeFile as fsWriteFile,
|
|
43
43
|
} from "node:fs/promises";
|
|
44
|
-
import { dirname, join, resolve } from "node:path";
|
|
44
|
+
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";
|
|
@@ -75,7 +75,7 @@ import {
|
|
|
75
75
|
import { FileSystemStoryReflectionAdapter } from "./phase-dependency-model/infrastructure/filesystem/file-system-story-reflection-adapter.js";
|
|
76
76
|
import { StoryReflectionStatusPresenter } from "./phase-dependency-model/presentation/cli/story-reflection-status-presenter.js";
|
|
77
77
|
import { buildPhase2Extensions } from "./phase2-extensions/composition-root.js";
|
|
78
|
-
import { createQuickModeCompositionRoot } from "./quick-mode/composition-root.js";
|
|
78
|
+
import { createQuickModeCompositionRoot, type QuickModeCompositionRootOptions } from "./quick-mode/composition-root.js";
|
|
79
79
|
import { buildRegressionSuite } from "./regression-suite/composition-root.js";
|
|
80
80
|
import type { SkillSet } from "./setup/skill-deployer.js";
|
|
81
81
|
import {
|
|
@@ -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;
|
|
@@ -228,7 +241,7 @@ Commands:
|
|
|
228
241
|
|
|
229
242
|
lint Run lint checks (--json, --target <path>)
|
|
230
243
|
|
|
231
|
-
validate Run validators (--layer L2|L3|L4|all; L0 prints runtime hook info, --unit, --format human|agent|ci|json, --json)
|
|
244
|
+
validate Run validators (--layer L0|L2|L3|L4|all; L0 prints runtime hook info, --unit, --format human|agent|ci|json, --json)
|
|
232
245
|
ci-check CI check (--quick for quick mode, --fail-on-reject, --dry-run, --files)
|
|
233
246
|
check-change-category Classify changed paths for quick mode (--paths <csv>, --format human|json)
|
|
234
247
|
|
|
@@ -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
|
|
@@ -501,6 +519,15 @@ function parseValidateFormat(args: readonly string[]): "human" | "agent" | "ci"
|
|
|
501
519
|
throw new Error(`Invalid --format value for validate: '${raw}'. Supported values: human, agent, ci, json.`);
|
|
502
520
|
}
|
|
503
521
|
|
|
522
|
+
function parseValidateLayer(args: readonly string[]): "L0" | "L2" | "L3" | "L4" | "all" | undefined {
|
|
523
|
+
const raw = parseFlag(args, "--layer");
|
|
524
|
+
if (raw === undefined) return undefined;
|
|
525
|
+
if (raw === "L0" || raw === "L2" || raw === "L3" || raw === "L4" || raw === "all") return raw;
|
|
526
|
+
|
|
527
|
+
const lintGuidance = raw === "L1" ? "\nL1 は `npx phasegate lint` で実行してください。" : "";
|
|
528
|
+
throw new Error(`不正な --layer 値: ${raw}\n有効値: L0, L2, L3, L4, all${lintGuidance}`);
|
|
529
|
+
}
|
|
530
|
+
|
|
504
531
|
function levenshtein(a: string, b: string): number {
|
|
505
532
|
const m = a.length;
|
|
506
533
|
const n = b.length;
|
|
@@ -553,12 +580,25 @@ function validateKnownFlags(args: readonly string[], known: readonly string[]):
|
|
|
553
580
|
return null;
|
|
554
581
|
}
|
|
555
582
|
|
|
583
|
+
/**
|
|
584
|
+
* Full Mode session が許可する変更カテゴリ。
|
|
585
|
+
*
|
|
586
|
+
* WI-348: 照合相手は quick-mode の `ChangeCategoryValue`
|
|
587
|
+
* (`bugfix | docs | test | config | feature | domain | api`) であり、
|
|
588
|
+
* レイヤー名(application / infrastructure / presentation)ではない。
|
|
589
|
+
* 旧実装はレイヤー名語彙を書き出していたため、交差が domain / config のみとなり
|
|
590
|
+
* session を張っても feature / api の書き込みがブロックされ続けていた。
|
|
591
|
+
* ChangeCategory の全語彙を許可し、session のスコープ制御は
|
|
592
|
+
* unit / 期限 / target path 側の判定に委ねる。
|
|
593
|
+
*/
|
|
556
594
|
const FULL_MODE_SESSION_ALLOWED_CATEGORIES = Object.freeze([
|
|
557
|
-
"
|
|
558
|
-
"
|
|
559
|
-
"
|
|
560
|
-
"presentation",
|
|
595
|
+
"bugfix",
|
|
596
|
+
"docs",
|
|
597
|
+
"test",
|
|
561
598
|
"config",
|
|
599
|
+
"feature",
|
|
600
|
+
"domain",
|
|
601
|
+
"api",
|
|
562
602
|
]);
|
|
563
603
|
|
|
564
604
|
interface FullModeSessionFile {
|
|
@@ -878,6 +918,33 @@ Options:
|
|
|
878
918
|
--force Overwrite an existing target when applying.
|
|
879
919
|
--json Output machine-readable JSON.
|
|
880
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.`,
|
|
881
948
|
session: `Usage: phasegate session <begin|end> [options]
|
|
882
949
|
|
|
883
950
|
Manage hook-visible Full Mode sessions.
|
|
@@ -1901,6 +1968,32 @@ async function loadResolvedConfig(command?: string): Promise<HarnessConfigV2 | u
|
|
|
1901
1968
|
}
|
|
1902
1969
|
}
|
|
1903
1970
|
|
|
1971
|
+
/**
|
|
1972
|
+
* WI-351: quick-mode composition root へ解決済み configPath / rootDir を注入する。
|
|
1973
|
+
*
|
|
1974
|
+
* 無指定だと `HarnessConfigQuickModeConfigAdapter` は `process.cwd()/phasegate.config.json`、
|
|
1975
|
+
* `FsFileExistenceAdapter` は `process.cwd()` を基準にする。サブディレクトリから CLI を
|
|
1976
|
+
* 実行すると config を見失い、相対パスの存在判定(= CREATE/MODIFY 推定)も
|
|
1977
|
+
* プロジェクトルート基準の hook とずれて分類結果が食い違う。
|
|
1978
|
+
* config-foundation が上方探索で解決した sourcePath を基準に揃える
|
|
1979
|
+
* (hook 側で WI-346 が行ったのと同じ整合)。
|
|
1980
|
+
* config 未検出・不正時は従来どおり cwd 基準へフォールバックする(fail-open)。
|
|
1981
|
+
*/
|
|
1982
|
+
async function resolveQuickModeCompositionOptions(): Promise<QuickModeCompositionRootOptions> {
|
|
1983
|
+
try {
|
|
1984
|
+
const configModule = createConfigFoundationModule();
|
|
1985
|
+
const { sourcePath } = await configModule.usecases.loadResolvedConfigUseCase.execute();
|
|
1986
|
+
if (typeof sourcePath === "string" && sourcePath !== "") {
|
|
1987
|
+
const configDir = dirname(sourcePath);
|
|
1988
|
+
const rootDir = basename(configDir) === ".phasegate-local" ? dirname(configDir) : configDir;
|
|
1989
|
+
return { configPath: sourcePath, rootDir };
|
|
1990
|
+
}
|
|
1991
|
+
} catch {
|
|
1992
|
+
// 解決できない場合はフォールバック(下の return)
|
|
1993
|
+
}
|
|
1994
|
+
return { rootDir: getProjectRoot() };
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1904
1997
|
async function loadWorldResolvedConfig() {
|
|
1905
1998
|
try {
|
|
1906
1999
|
const configModule = createConfigFoundationModule();
|
|
@@ -2888,7 +2981,7 @@ async function main(): Promise<void> {
|
|
|
2888
2981
|
// ── validator-system ──
|
|
2889
2982
|
case "validate": {
|
|
2890
2983
|
const mod = createValidatorSystemModule(toValidatorSystemConfig(resolvedConfig));
|
|
2891
|
-
const layer =
|
|
2984
|
+
const layer = parseValidateLayer(args);
|
|
2892
2985
|
const unit = parseFlag(args, "--unit");
|
|
2893
2986
|
const phase = parseFlag(args, "--phase");
|
|
2894
2987
|
const format = parseValidateFormat(args) ?? (json ? "ci" : undefined);
|
|
@@ -2917,7 +3010,10 @@ async function main(): Promise<void> {
|
|
|
2917
3010
|
case "ci-check": {
|
|
2918
3011
|
const quick = hasFlag(args, "--quick");
|
|
2919
3012
|
if (quick) {
|
|
2920
|
-
|
|
3013
|
+
// WI-364: check-change-category (WI-351) と同様に解決済み configPath / rootDir を注入する。
|
|
3014
|
+
// 無指定だと quickMode 設定を cwd 基準でしか探せず、サブディレクトリ実行で
|
|
3015
|
+
// HarnessConfigNotFoundError → exit 2 になっていた。
|
|
3016
|
+
const mod = createQuickModeCompositionRoot(await resolveQuickModeCompositionOptions());
|
|
2921
3017
|
const failOnReject = hasFlag(args, "--fail-on-reject");
|
|
2922
3018
|
const dryRun = hasFlag(args, "--dry-run");
|
|
2923
3019
|
const files = parseFlag(args, "--files");
|
|
@@ -2956,7 +3052,7 @@ async function main(): Promise<void> {
|
|
|
2956
3052
|
);
|
|
2957
3053
|
return;
|
|
2958
3054
|
}
|
|
2959
|
-
const mod = createQuickModeCompositionRoot();
|
|
3055
|
+
const mod = createQuickModeCompositionRoot(await resolveQuickModeCompositionOptions());
|
|
2960
3056
|
const paths = parseFlag(args, "--paths");
|
|
2961
3057
|
const format = parseFlag(args, "--format") as "human" | "json" | undefined;
|
|
2962
3058
|
const failOnFullRequired = hasFlag(args, "--fail-on-full-required");
|
|
@@ -3193,7 +3289,7 @@ Examples:
|
|
|
3193
3289
|
console.error(flagError);
|
|
3194
3290
|
process.exit(2);
|
|
3195
3291
|
}
|
|
3196
|
-
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
3292
|
+
const mod = buildCiGovernance(rootDir, harnessRoot, toCiGovernanceDocPaths(resolvedConfig));
|
|
3197
3293
|
const presetId = parseFlag(args, "--preset") ?? "standard";
|
|
3198
3294
|
const templateType = parseFlag(args, "--type") ?? "aidlc-gate";
|
|
3199
3295
|
const render = hasFlag(args, "--render");
|
|
@@ -3205,7 +3301,7 @@ Examples:
|
|
|
3205
3301
|
}
|
|
3206
3302
|
|
|
3207
3303
|
case "ci:migrate-agents-md": {
|
|
3208
|
-
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
3304
|
+
const mod = buildCiGovernance(rootDir, harnessRoot, toCiGovernanceDocPaths(resolvedConfig));
|
|
3209
3305
|
const dryRun = hasFlag(args, "--dry-run");
|
|
3210
3306
|
const validateOnly = hasFlag(args, "--validate-only");
|
|
3211
3307
|
const format = json ? "json" : "human";
|
|
@@ -3216,7 +3312,7 @@ Examples:
|
|
|
3216
3312
|
}
|
|
3217
3313
|
|
|
3218
3314
|
case "ci:auto-refresh-agent-context": {
|
|
3219
|
-
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
3315
|
+
const mod = buildCiGovernance(rootDir, harnessRoot, toCiGovernanceDocPaths(resolvedConfig));
|
|
3220
3316
|
const dryRun = hasFlag(args, "--dry-run");
|
|
3221
3317
|
const apply = hasFlag(args, "--apply");
|
|
3222
3318
|
const format = json ? "json" : "human";
|
|
@@ -3227,7 +3323,7 @@ Examples:
|
|
|
3227
3323
|
}
|
|
3228
3324
|
|
|
3229
3325
|
case "refresh-claude-md": {
|
|
3230
|
-
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
3326
|
+
const mod = buildCiGovernance(rootDir, harnessRoot, toCiGovernanceDocPaths(resolvedConfig));
|
|
3231
3327
|
const dryRun = hasFlag(args, "--dry-run");
|
|
3232
3328
|
const apply = hasFlag(args, "--apply");
|
|
3233
3329
|
const format = json ? "json" : "human";
|
|
@@ -3238,7 +3334,7 @@ Examples:
|
|
|
3238
3334
|
}
|
|
3239
3335
|
|
|
3240
3336
|
case "p2:check-agent-context": {
|
|
3241
|
-
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
3337
|
+
const mod = buildCiGovernance(rootDir, harnessRoot, toCiGovernanceDocPaths(resolvedConfig));
|
|
3242
3338
|
const thresholdRaw = parseFlag(args, "--threshold-days");
|
|
3243
3339
|
const thresholdDays = thresholdRaw === undefined ? undefined : Number(thresholdRaw);
|
|
3244
3340
|
const format = json ? "json" : "human";
|
|
@@ -3249,7 +3345,7 @@ Examples:
|
|
|
3249
3345
|
}
|
|
3250
3346
|
|
|
3251
3347
|
case "ci:check-repetition": {
|
|
3252
|
-
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
3348
|
+
const mod = buildCiGovernance(rootDir, harnessRoot, toCiGovernanceDocPaths(resolvedConfig));
|
|
3253
3349
|
const errorCode = parseFlag(args, "--code") ?? "";
|
|
3254
3350
|
const reset = hasFlag(args, "--reset");
|
|
3255
3351
|
const format = json ? "json" : "human";
|
|
@@ -3260,7 +3356,7 @@ Examples:
|
|
|
3260
3356
|
}
|
|
3261
3357
|
|
|
3262
3358
|
case "baseline": {
|
|
3263
|
-
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
3359
|
+
const mod = buildCiGovernance(rootDir, harnessRoot, toCiGovernanceDocPaths(resolvedConfig));
|
|
3264
3360
|
const dryRun = hasFlag(args, "--dry-run");
|
|
3265
3361
|
const force = hasFlag(args, "--force");
|
|
3266
3362
|
const pathsFlag = parseFlag(args, "--paths");
|
|
@@ -3283,7 +3379,7 @@ Examples:
|
|
|
3283
3379
|
}
|
|
3284
3380
|
|
|
3285
3381
|
case "scaffold-design": {
|
|
3286
|
-
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
3382
|
+
const mod = buildCiGovernance(rootDir, harnessRoot, toCiGovernanceDocPaths(resolvedConfig));
|
|
3287
3383
|
const unit = parseFlag(args, "--unit") ?? "";
|
|
3288
3384
|
const phase = parseFlag(args, "--phase") ?? "";
|
|
3289
3385
|
const dryRun = hasFlag(args, "--dry-run");
|
|
@@ -3303,8 +3399,50 @@ Examples:
|
|
|
3303
3399
|
return;
|
|
3304
3400
|
}
|
|
3305
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
|
+
|
|
3306
3444
|
case "integrity:pin": {
|
|
3307
|
-
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
3445
|
+
const mod = buildCiGovernance(rootDir, harnessRoot, toCiGovernanceDocPaths(resolvedConfig));
|
|
3308
3446
|
const dryRun = hasFlag(args, "--dry-run");
|
|
3309
3447
|
const format = json ? "json" : "human";
|
|
3310
3448
|
const result = await mod.integrityHandler.pin({ dryRun, format });
|
|
@@ -3314,7 +3452,7 @@ Examples:
|
|
|
3314
3452
|
}
|
|
3315
3453
|
|
|
3316
3454
|
case "integrity:verify": {
|
|
3317
|
-
const mod = buildCiGovernance(rootDir, harnessRoot);
|
|
3455
|
+
const mod = buildCiGovernance(rootDir, harnessRoot, toCiGovernanceDocPaths(resolvedConfig));
|
|
3318
3456
|
const format = json ? "json" : "human";
|
|
3319
3457
|
const result = await mod.integrityHandler.verify({ format });
|
|
3320
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,22 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @layer infrastructure
|
|
3
3
|
* @unit phase-dependency-model
|
|
4
|
+
* @work-item-id WI-369
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
import { readFile } from
|
|
7
|
-
import * as path from
|
|
8
|
-
import type { PlanDocumentReaderPort } from
|
|
9
|
-
import type
|
|
10
|
-
import type {
|
|
11
|
-
import { PlanEvidence } from
|
|
7
|
+
import { readFile } from "node:fs/promises";
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
import type { PlanDocumentReaderPort } from "../../domain/ports/plan-document-reader-port.js";
|
|
10
|
+
import { DEFAULT_PATH_ROOTS, type PathRoots } from "../../domain/values/artifact.js";
|
|
11
|
+
import type { PhaseNode } from "../../domain/values/phase-node.js";
|
|
12
|
+
import { PlanEvidence } from "../../domain/values/plan-evidence.js";
|
|
13
|
+
import type { PlanningMode } from "../../domain/values/planning-mode.js";
|
|
12
14
|
|
|
13
15
|
export interface MarkdownPlanDocumentReaderDeps {
|
|
14
16
|
readonly rootDir: string;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* WI-358 (issue #29): 見出しの表記ゆれで Planning Mode の evidence 判定が落ちる罠を潰す。
|
|
21
|
+
*
|
|
22
|
+
* 旧パターンは "QA" 綴りしか受け付けず、エージェントが自然に書く `## Q&A` /
|
|
23
|
+
* 全角の `## Q&A` を弾いていた。中身は同一なのに phase gate だけが落ち、
|
|
24
|
+
* 原因が見出しの綴りだと気付けないまま止まる。
|
|
25
|
+
*
|
|
26
|
+
* 緩和方向のみの変更であり、既存の `## QA` / `## 4. QA(不明点・確認事項)` は
|
|
27
|
+
* すべて従来どおりマッチする。
|
|
28
|
+
*/
|
|
29
|
+
const QA_SECTION_PATTERN = /^##+\s*(?:\d+[..]\s*)?Q[&&]?A\b/m;
|
|
30
|
+
const QUESTION_PATTERN = /^(?:Q:|###\s*\[Question\])/gm;
|
|
31
|
+
const LEGACY_ANSWER_PATTERN = /^A:/gm;
|
|
32
|
+
const STRUCTURED_ANSWER_PATTERN = /^\[Answer\][ \t]*$/gm;
|
|
33
|
+
const ANSWER_BODY_BOUNDARY_PATTERN = /^(?:###\s*\[Question\]|#{1,6}\s+)/m;
|
|
20
34
|
|
|
21
35
|
export class MarkdownPlanDocumentReader implements PlanDocumentReaderPort {
|
|
22
36
|
private readonly rootDir: string;
|
|
@@ -29,6 +43,7 @@ export class MarkdownPlanDocumentReader implements PlanDocumentReaderPort {
|
|
|
29
43
|
node: PhaseNode,
|
|
30
44
|
scope: { unitId?: string; storyId?: string },
|
|
31
45
|
expectedMode: PlanningMode,
|
|
46
|
+
pathRoots: PathRoots = DEFAULT_PATH_ROOTS,
|
|
32
47
|
): Promise<PlanEvidence> {
|
|
33
48
|
const planArtifacts = node.planArtifacts();
|
|
34
49
|
|
|
@@ -43,7 +58,7 @@ export class MarkdownPlanDocumentReader implements PlanDocumentReaderPort {
|
|
|
43
58
|
const artifact = planArtifacts[0];
|
|
44
59
|
let resolvedPath: string;
|
|
45
60
|
try {
|
|
46
|
-
resolvedPath = artifact.resolve(scope);
|
|
61
|
+
resolvedPath = artifact.resolve(scope, pathRoots);
|
|
47
62
|
} catch {
|
|
48
63
|
return PlanEvidence.create({
|
|
49
64
|
exists: false,
|
|
@@ -55,7 +70,7 @@ export class MarkdownPlanDocumentReader implements PlanDocumentReaderPort {
|
|
|
55
70
|
|
|
56
71
|
let content: string;
|
|
57
72
|
try {
|
|
58
|
-
content = await readFile(absolutePath,
|
|
73
|
+
content = await readFile(absolutePath, "utf8");
|
|
59
74
|
} catch {
|
|
60
75
|
return PlanEvidence.create({
|
|
61
76
|
exists: false,
|
|
@@ -66,11 +81,7 @@ export class MarkdownPlanDocumentReader implements PlanDocumentReaderPort {
|
|
|
66
81
|
|
|
67
82
|
const hasQaSection = QA_SECTION_PATTERN.test(content);
|
|
68
83
|
const qaComplete = this.isQaComplete(content, hasQaSection, expectedMode);
|
|
69
|
-
const planningModeMatch = this.detectPlanningModeMatch(
|
|
70
|
-
content,
|
|
71
|
-
hasQaSection,
|
|
72
|
-
expectedMode,
|
|
73
|
-
);
|
|
84
|
+
const planningModeMatch = this.detectPlanningModeMatch(content, hasQaSection, expectedMode);
|
|
74
85
|
|
|
75
86
|
return PlanEvidence.create({
|
|
76
87
|
exists: true,
|
|
@@ -79,16 +90,12 @@ export class MarkdownPlanDocumentReader implements PlanDocumentReaderPort {
|
|
|
79
90
|
});
|
|
80
91
|
}
|
|
81
92
|
|
|
82
|
-
private isQaComplete(
|
|
83
|
-
content: string,
|
|
84
|
-
hasQaSection: boolean,
|
|
85
|
-
expectedMode: PlanningMode,
|
|
86
|
-
): boolean {
|
|
93
|
+
private isQaComplete(content: string, hasQaSection: boolean, expectedMode: PlanningMode): boolean {
|
|
87
94
|
if (expectedMode.requiresAnsweredQa()) {
|
|
88
95
|
if (!hasQaSection) return false;
|
|
89
96
|
const qaSection = this.extractQaSection(content);
|
|
90
97
|
const questionCount = (qaSection.match(QUESTION_PATTERN) ?? []).length;
|
|
91
|
-
const answerCount = (qaSection
|
|
98
|
+
const answerCount = this.countAnswers(qaSection);
|
|
92
99
|
return questionCount > 0 && questionCount === answerCount;
|
|
93
100
|
}
|
|
94
101
|
|
|
@@ -99,16 +106,12 @@ export class MarkdownPlanDocumentReader implements PlanDocumentReaderPort {
|
|
|
99
106
|
return true;
|
|
100
107
|
}
|
|
101
108
|
|
|
102
|
-
private detectPlanningModeMatch(
|
|
103
|
-
content: string,
|
|
104
|
-
hasQaSection: boolean,
|
|
105
|
-
expectedMode: PlanningMode,
|
|
106
|
-
): boolean {
|
|
109
|
+
private detectPlanningModeMatch(content: string, hasQaSection: boolean, expectedMode: PlanningMode): boolean {
|
|
107
110
|
if (expectedMode.requiresAnsweredQa()) {
|
|
108
111
|
if (!hasQaSection) return false;
|
|
109
112
|
const qaSection = this.extractQaSection(content);
|
|
110
113
|
const questionCount = (qaSection.match(QUESTION_PATTERN) ?? []).length;
|
|
111
|
-
const answerCount = (qaSection
|
|
114
|
+
const answerCount = this.countAnswers(qaSection);
|
|
112
115
|
return questionCount > 0 && answerCount === questionCount;
|
|
113
116
|
}
|
|
114
117
|
|
|
@@ -121,14 +124,42 @@ export class MarkdownPlanDocumentReader implements PlanDocumentReaderPort {
|
|
|
121
124
|
|
|
122
125
|
private extractQaSection(content: string): string {
|
|
123
126
|
const match = content.match(QA_SECTION_PATTERN);
|
|
124
|
-
if (!match || match.index === undefined) return
|
|
127
|
+
if (!match || match.index === undefined) return "";
|
|
125
128
|
|
|
126
129
|
const start = match.index;
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
const sectionContentStart = start + match[0].length;
|
|
131
|
+
const sectionContent = content.slice(sectionContentStart);
|
|
132
|
+
const headingMarker = match[0].match(/^##+/)?.[0];
|
|
133
|
+
if (!headingMarker) return "";
|
|
134
|
+
|
|
135
|
+
const sameLevelHeadingPattern = new RegExp(`^${headingMarker}(?!#)[ \\t]+(.+)$`, "gm");
|
|
136
|
+
let end = content.length;
|
|
137
|
+
for (const headingMatch of sectionContent.matchAll(sameLevelHeadingPattern)) {
|
|
138
|
+
if (/^\[Question\](?:\s|$)/.test(headingMatch[1].trimStart())) continue;
|
|
139
|
+
if (headingMatch.index !== undefined) {
|
|
140
|
+
end = sectionContentStart + headingMatch.index;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
131
144
|
|
|
132
145
|
return content.slice(start, end);
|
|
133
146
|
}
|
|
147
|
+
|
|
148
|
+
private countAnswers(qaSection: string): number {
|
|
149
|
+
const legacyAnswerCount = (qaSection.match(LEGACY_ANSWER_PATTERN) ?? []).length;
|
|
150
|
+
let structuredAnswerCount = 0;
|
|
151
|
+
|
|
152
|
+
for (const answerMatch of qaSection.matchAll(STRUCTURED_ANSWER_PATTERN)) {
|
|
153
|
+
if (answerMatch.index === undefined) continue;
|
|
154
|
+
const bodyStart = answerMatch.index + answerMatch[0].length;
|
|
155
|
+
const followingContent = qaSection.slice(bodyStart);
|
|
156
|
+
const boundaryMatch = followingContent.match(ANSWER_BODY_BOUNDARY_PATTERN);
|
|
157
|
+
const answerBody =
|
|
158
|
+
boundaryMatch?.index === undefined ? followingContent : followingContent.slice(0, boundaryMatch.index);
|
|
159
|
+
const hasBody = answerBody.split(/\r?\n/).some((line) => line.trim().length > 0);
|
|
160
|
+
if (hasBody) structuredAnswerCount += 1;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return legacyAnswerCount + structuredAnswerCount;
|
|
164
|
+
}
|
|
134
165
|
}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* @layer presentation
|
|
3
3
|
* @unit phase2-extensions
|
|
4
4
|
*/
|
|
5
|
-
import type { CheckDocFreshnessUseCase } from
|
|
6
|
-
import { FreshnessResultFormatter } from
|
|
5
|
+
import type { CheckDocFreshnessUseCase } from "../../application/usecases/check-doc-freshness-usecase.js";
|
|
6
|
+
import { FreshnessResultFormatter } from "../formatters/freshness-result-formatter.js";
|
|
7
7
|
|
|
8
8
|
export class CheckFreshnessHandler {
|
|
9
9
|
private readonly formatter = new FreshnessResultFormatter();
|
|
@@ -12,26 +12,34 @@ export class CheckFreshnessHandler {
|
|
|
12
12
|
|
|
13
13
|
async handle(args: string[]): Promise<{ exitCode: number; stdout: string }> {
|
|
14
14
|
let targetPattern: string | undefined;
|
|
15
|
-
let format:
|
|
15
|
+
let format: "text" | "json" = "text";
|
|
16
16
|
let dryRun = false;
|
|
17
17
|
|
|
18
18
|
for (let index = 0; index < args.length; index += 1) {
|
|
19
19
|
const arg = args[index];
|
|
20
|
-
if (arg ===
|
|
20
|
+
if (arg === "--pattern") {
|
|
21
21
|
targetPattern = args[index + 1];
|
|
22
22
|
index += 1;
|
|
23
|
-
} else if (arg ===
|
|
24
|
-
format = (args[index + 1] as
|
|
23
|
+
} else if (arg === "--format") {
|
|
24
|
+
format = (args[index + 1] as "text" | "json") ?? "text";
|
|
25
25
|
index += 1;
|
|
26
|
-
} else if (arg ===
|
|
26
|
+
} else if (arg === "--dry-run") {
|
|
27
27
|
dryRun = true;
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
const result = await this.useCase.execute({ targetPattern, format, dryRun });
|
|
32
|
+
// WI-359: `--dry-run` は「副作用なしで診断のみ」(skills/doc-health-checker/SKILL.md)であり、
|
|
33
|
+
// シナリオ仕様 SC-P2-002 の期待結果も exit 0 である。従来は dryRun をパースだけして
|
|
34
|
+
// 捨てており、error 閾値超過の文書が 1 件でもあると診断のみの実行でも exit 1 になっていた。
|
|
35
|
+
// その結果 SC-P2-002 の e2e は「コードを一切変えなくても暦の経過だけで落ちる」状態になっていた。
|
|
36
|
+
// 実運用の鮮度ゲートは L4-004(`validate --layer L4`)が担い、そちらは本 handler を
|
|
37
|
+
// 経由しないため、dry-run を report-only にしてもゲートは緩まない。
|
|
38
|
+
// フラグ未指定時の exit 1 は従来どおり維持する。
|
|
39
|
+
const exitCode = dryRun ? 0 : result.summary.error > 0 ? 1 : 0;
|
|
32
40
|
return {
|
|
33
|
-
exitCode
|
|
34
|
-
stdout: format ===
|
|
41
|
+
exitCode,
|
|
42
|
+
stdout: format === "json" ? this.formatter.formatJson(result) : this.formatter.formatText(result),
|
|
35
43
|
};
|
|
36
44
|
}
|
|
37
45
|
}
|