phasegate 0.191.0 → 0.212.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.ja.md +52 -15
  3. package/README.md +39 -11
  4. package/docs/ADR/030-injection-threat-model-and-trust-root.md +145 -0
  5. package/docs/guide/hooks-integration.md +50 -1
  6. package/docs/guide/installation.md +1 -1
  7. package/docs/guide/quick-vs-full-mode.md +1 -1
  8. package/docs/guide/skills-overview.md +7 -8
  9. package/package.json +1 -1
  10. package/scripts/harness/agent-integration/presentation/phasegate-status-context.ts +131 -63
  11. package/scripts/harness/agent-integration/presentation/session-start-hook.ts +31 -4
  12. package/scripts/harness/agent-integration/presentation/spotlight.ts +65 -0
  13. package/scripts/harness/ci-governance/application/dto/pin-integrity-input.ts +8 -0
  14. package/scripts/harness/ci-governance/application/dto/pin-integrity-output.ts +9 -0
  15. package/scripts/harness/ci-governance/application/dto/verify-integrity-input.ts +7 -0
  16. package/scripts/harness/ci-governance/application/dto/verify-integrity-output.ts +10 -0
  17. package/scripts/harness/ci-governance/application/usecases/pin-integrity-usecase.ts +60 -0
  18. package/scripts/harness/ci-governance/application/usecases/verify-integrity-usecase.ts +46 -0
  19. package/scripts/harness/ci-governance/composition-root.ts +67 -64
  20. package/scripts/harness/ci-governance/domain/ports/integrity-manifest-repository-port.ts +14 -0
  21. package/scripts/harness/ci-governance/domain/ports/sha256-hasher-port.ts +10 -0
  22. package/scripts/harness/ci-governance/domain/services/integrity-checker.ts +42 -0
  23. package/scripts/harness/ci-governance/domain/value-objects/integrity-drift.ts +16 -0
  24. package/scripts/harness/ci-governance/domain/value-objects/integrity-manifest.ts +49 -0
  25. package/scripts/harness/ci-governance/domain/value-objects/integrity-target.ts +40 -0
  26. package/scripts/harness/ci-governance/infrastructure/adapters/file-system-sha256-hasher-adapter.ts +17 -0
  27. package/scripts/harness/ci-governance/infrastructure/adapters/harness-api-command-existence-adapter.ts +7 -73
  28. package/scripts/harness/ci-governance/infrastructure/adapters/integrity-manifest-json-repository-adapter.ts +83 -0
  29. package/scripts/harness/ci-governance/presentation/handlers/integrity-handler.ts +76 -0
  30. package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +53 -28
  31. package/scripts/harness/harness-api/domain/value-objects/ci-check-result.ts +33 -8
  32. package/scripts/harness/harness-api/domain/value-objects/known-harness-commands.ts +90 -0
  33. package/scripts/harness/installation/application/bundled-skill-selection.ts +2 -5
  34. package/scripts/harness/main.ts +257 -105
  35. package/scripts/harness/phase-dependency-model/infrastructure/filesystem/file-system-story-reflection-adapter.ts +65 -3
  36. package/scripts/harness/quick-mode/domain/services/quick-mode-judgment-engine.ts +44 -40
  37. package/scripts/harness/setup/skill-deployer.ts +2 -4
  38. package/scripts/harness/validator-system/application/use-cases/run-l2-validators-usecase.ts +82 -49
  39. package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +87 -58
  40. package/scripts/harness/validator-system/composition-root.ts +141 -99
  41. package/scripts/harness/validator-system/domain/ports/coverage-attestation-gating-policy-port.ts +14 -0
  42. package/scripts/harness/validator-system/domain/ports/injection-scan-policy-port.ts +14 -0
  43. package/scripts/harness/validator-system/domain/services/coverage-attestation-gating-service.ts +56 -0
  44. package/scripts/harness/validator-system/domain/services/injection-pattern-scan-service.ts +118 -0
  45. package/scripts/harness/validator-system/domain/value-objects/coverage-gating-report.ts +67 -0
  46. package/scripts/harness/validator-system/domain/value-objects/injection-scan-report.ts +55 -0
  47. package/scripts/harness/validator-system/domain/value-objects/validator-id.ts +35 -31
  48. package/scripts/harness/validator-system/infrastructure/adapters/adr-foundation-reference-adapter.ts +13 -7
  49. package/scripts/harness/validator-system/infrastructure/adapters/file-system-coverage-attestation-gating-adapter.ts +87 -0
  50. package/scripts/harness/validator-system/infrastructure/adapters/file-system-injection-scan-adapter.ts +82 -0
  51. package/skills/README.md +1 -1
  52. package/skills/codebase-mapper/SKILL.md +1 -1
  53. package/skills/consistency-checker/references//343/203/201/343/202/247/343/203/203/343/202/257/343/203/252/343/202/271/343/203/210.md +1 -1
  54. package/skills/doc-health-checker/SKILL.md +148 -0
  55. package/skills/release-publisher/SKILL.md +101 -0
  56. package/skills/skill-creator/SKILL.md +74 -332
  57. package/skills/story-implementor/SKILL.md +52 -0
  58. package/skills/story-mapper/SKILL.md +4 -0
  59. package/skills/story-writer/SKILL.md +9 -0
  60. package/skills/uiux-designer/references/uiux-design-template.md +4 -4
  61. package/skills/unit-designer/SKILL.md +3 -1
  62. package/skills/doc-freshness-checker/SKILL.md +0 -140
  63. package/skills/implementation-planner/SKILL.md +0 -169
  64. package/skills/implementation-planner/references/document-structure.md +0 -116
  65. package/skills/implementation-planner/references/plan-template.md +0 -177
  66. package/skills/implementation-planner/references/workflow.md +0 -164
  67. package/skills/pointer-validator/SKILL.md +0 -105
@@ -5,53 +5,54 @@
5
5
  * Composition Root - 依存性の組み立て
6
6
  */
7
7
 
8
- import { TemplateGenerator } from './domain/services/template-generator.js';
9
- import { RepetitionDetector } from './domain/services/repetition-detector.js';
10
- import { PointerValidator } from './domain/services/pointer-validator.js';
11
- import { LessonAggregator } from './domain/services/lesson-aggregator.js';
12
- import { ClaudeMdComposer } from './domain/services/claude-md-composer.js';
13
-
14
- import { GenerateCiTemplateUseCase } from './application/usecases/generate-ci-template-usecase.js';
15
- import { RenderCiTemplateUseCase } from './application/usecases/render-ci-template-usecase.js';
16
- import { RecordErrorOccurrenceUseCase } from './application/usecases/record-error-occurrence-usecase.js';
17
- import { CheckEscalationUseCase } from './application/usecases/check-escalation-usecase.js';
18
- import { ResetRepetitionUseCase } from './application/usecases/reset-repetition-usecase.js';
19
- import { MigrateAgentsMdUseCase } from './application/usecases/migrate-agents-md-usecase.js';
20
- import { RefreshAgentContextUseCase } from './application/usecases/refresh-agent-context-usecase.js';
21
- import { RefreshClaudeMdUseCase } from './application/usecases/refresh-claude-md-usecase.js';
22
- import { CheckAgentContextUseCase } from './application/usecases/check-agent-context-usecase.js';
23
- import { AggregateLessonsUseCase } from './application/usecases/aggregate-lessons-usecase.js';
24
- import { ValidatePointersUseCase } from './application/usecases/validate-pointers-usecase.js';
25
-
26
- import { ValidatorIdRegistryAdapter } from './infrastructure/adapters/validator-id-registry-adapter.js';
27
- import { PresetConfigAdapter } from './infrastructure/adapters/preset-config-adapter.js';
28
- import { ErrorRepetitionJsonRepository } from './infrastructure/adapters/error-repetition-json-repository.js';
29
- import { EscalationLogExecutorAdapter } from './infrastructure/adapters/escalation-log-executor-adapter.js';
30
- import { YamlTemplateRendererAdapter } from './infrastructure/adapters/yaml-template-renderer-adapter.js';
31
- import { FileSystemExistenceAdapter } from './infrastructure/adapters/file-system-existence-adapter.js';
32
- import { AgentsMdFileAdapter } from './infrastructure/adapters/agents-md-file-adapter.js';
33
- import { AgentContextFileAdapter } from './infrastructure/adapters/agent-context-file-adapter.js';
34
- import { LessonArtifactFileReaderAdapter } from './infrastructure/adapters/lesson-artifact-file-reader-adapter.js';
35
- import { HarnessApiCommandExistenceAdapter } from './infrastructure/adapters/harness-api-command-existence-adapter.js';
36
- import { AdrFoundationExistenceAdapter } from './infrastructure/adapters/adr-foundation-existence-adapter.js';
37
-
38
- import { GenerateCiTemplateHandler } from './presentation/handlers/generate-ci-template-handler.js';
39
- import { MigrateAgentsMdHandler } from './presentation/handlers/migrate-agents-md-handler.js';
40
- import { RefreshAgentContextHandler } from './presentation/handlers/refresh-agent-context-handler.js';
41
- import { RefreshClaudeMdHandler } from './presentation/handlers/refresh-claude-md-handler.js';
42
- import { CheckAgentContextHandler } from './presentation/handlers/check-agent-context-handler.js';
43
- import { CheckRepetitionHandler } from './presentation/handlers/check-repetition-handler.js';
44
- import { CreateBaselineHandler } from './presentation/handlers/create-baseline-handler.js';
45
-
46
- import { CreateBaselineUseCase } from './application/usecases/create-baseline-usecase.js';
47
- import { GlobFileScannerAdapter } from './infrastructure/adapters/glob-file-scanner-adapter.js';
48
- import { FileSystemSha1HasherAdapter } from './infrastructure/adapters/file-system-sha1-hasher-adapter.js';
49
- import { BaselineJsonRepositoryAdapter } from './infrastructure/adapters/baseline-json-repository-adapter.js';
50
-
51
- import { ScaffoldDesignUseCase } from './application/usecases/scaffold-design-usecase.js';
52
- import { FileSystemTemplateRepositoryAdapter } from './infrastructure/adapters/file-system-template-repository-adapter.js';
53
- import { FileSystemDesignDocWriterAdapter } from './infrastructure/adapters/file-system-design-doc-writer-adapter.js';
54
- import { ScaffoldDesignHandler } from './presentation/handlers/scaffold-design-handler.js';
8
+ import { AggregateLessonsUseCase } from "./application/usecases/aggregate-lessons-usecase.js";
9
+ import { CheckAgentContextUseCase } from "./application/usecases/check-agent-context-usecase.js";
10
+ import { CheckEscalationUseCase } from "./application/usecases/check-escalation-usecase.js";
11
+ import { CreateBaselineUseCase } from "./application/usecases/create-baseline-usecase.js";
12
+ import { GenerateCiTemplateUseCase } from "./application/usecases/generate-ci-template-usecase.js";
13
+ import { MigrateAgentsMdUseCase } from "./application/usecases/migrate-agents-md-usecase.js";
14
+ import { PinIntegrityUseCase } from "./application/usecases/pin-integrity-usecase.js";
15
+ import { RecordErrorOccurrenceUseCase } from "./application/usecases/record-error-occurrence-usecase.js";
16
+ import { RefreshAgentContextUseCase } from "./application/usecases/refresh-agent-context-usecase.js";
17
+ import { RefreshClaudeMdUseCase } from "./application/usecases/refresh-claude-md-usecase.js";
18
+ import { RenderCiTemplateUseCase } from "./application/usecases/render-ci-template-usecase.js";
19
+ import { ResetRepetitionUseCase } from "./application/usecases/reset-repetition-usecase.js";
20
+ import { ScaffoldDesignUseCase } from "./application/usecases/scaffold-design-usecase.js";
21
+ import { ValidatePointersUseCase } from "./application/usecases/validate-pointers-usecase.js";
22
+ import { VerifyIntegrityUseCase } from "./application/usecases/verify-integrity-usecase.js";
23
+ import { ClaudeMdComposer } from "./domain/services/claude-md-composer.js";
24
+ import { IntegrityChecker } from "./domain/services/integrity-checker.js";
25
+ import { LessonAggregator } from "./domain/services/lesson-aggregator.js";
26
+ import { PointerValidator } from "./domain/services/pointer-validator.js";
27
+ import { RepetitionDetector } from "./domain/services/repetition-detector.js";
28
+ import { TemplateGenerator } from "./domain/services/template-generator.js";
29
+ import { AdrFoundationExistenceAdapter } from "./infrastructure/adapters/adr-foundation-existence-adapter.js";
30
+ import { AgentContextFileAdapter } from "./infrastructure/adapters/agent-context-file-adapter.js";
31
+ import { AgentsMdFileAdapter } from "./infrastructure/adapters/agents-md-file-adapter.js";
32
+ import { BaselineJsonRepositoryAdapter } from "./infrastructure/adapters/baseline-json-repository-adapter.js";
33
+ import { ErrorRepetitionJsonRepository } from "./infrastructure/adapters/error-repetition-json-repository.js";
34
+ import { EscalationLogExecutorAdapter } from "./infrastructure/adapters/escalation-log-executor-adapter.js";
35
+ import { FileSystemDesignDocWriterAdapter } from "./infrastructure/adapters/file-system-design-doc-writer-adapter.js";
36
+ import { FileSystemExistenceAdapter } from "./infrastructure/adapters/file-system-existence-adapter.js";
37
+ import { FileSystemSha1HasherAdapter } from "./infrastructure/adapters/file-system-sha1-hasher-adapter.js";
38
+ import { FileSystemSha256HasherAdapter } from "./infrastructure/adapters/file-system-sha256-hasher-adapter.js";
39
+ import { FileSystemTemplateRepositoryAdapter } from "./infrastructure/adapters/file-system-template-repository-adapter.js";
40
+ import { GlobFileScannerAdapter } from "./infrastructure/adapters/glob-file-scanner-adapter.js";
41
+ import { HarnessApiCommandExistenceAdapter } from "./infrastructure/adapters/harness-api-command-existence-adapter.js";
42
+ import { IntegrityManifestJsonRepositoryAdapter } from "./infrastructure/adapters/integrity-manifest-json-repository-adapter.js";
43
+ import { LessonArtifactFileReaderAdapter } from "./infrastructure/adapters/lesson-artifact-file-reader-adapter.js";
44
+ import { PresetConfigAdapter } from "./infrastructure/adapters/preset-config-adapter.js";
45
+ import { ValidatorIdRegistryAdapter } from "./infrastructure/adapters/validator-id-registry-adapter.js";
46
+ import { YamlTemplateRendererAdapter } from "./infrastructure/adapters/yaml-template-renderer-adapter.js";
47
+ import { CheckAgentContextHandler } from "./presentation/handlers/check-agent-context-handler.js";
48
+ import { CheckRepetitionHandler } from "./presentation/handlers/check-repetition-handler.js";
49
+ import { CreateBaselineHandler } from "./presentation/handlers/create-baseline-handler.js";
50
+ import { GenerateCiTemplateHandler } from "./presentation/handlers/generate-ci-template-handler.js";
51
+ import { IntegrityHandler } from "./presentation/handlers/integrity-handler.js";
52
+ import { MigrateAgentsMdHandler } from "./presentation/handlers/migrate-agents-md-handler.js";
53
+ import { RefreshAgentContextHandler } from "./presentation/handlers/refresh-agent-context-handler.js";
54
+ import { RefreshClaudeMdHandler } from "./presentation/handlers/refresh-claude-md-handler.js";
55
+ import { ScaffoldDesignHandler } from "./presentation/handlers/scaffold-design-handler.js";
55
56
 
56
57
  export interface CiGovernanceCompositionRoot {
57
58
  generateCiTemplateHandler: GenerateCiTemplateHandler;
@@ -70,12 +71,10 @@ export interface CiGovernanceCompositionRoot {
70
71
  validatePointersUseCase: ValidatePointersUseCase;
71
72
  createBaselineUseCase: CreateBaselineUseCase;
72
73
  scaffoldDesignUseCase: ScaffoldDesignUseCase;
74
+ integrityHandler: IntegrityHandler;
73
75
  }
74
76
 
75
- export function buildCiGovernance(
76
- baseDir: string,
77
- harnessRoot: string = baseDir,
78
- ): CiGovernanceCompositionRoot {
77
+ export function buildCiGovernance(baseDir: string, harnessRoot: string = baseDir): CiGovernanceCompositionRoot {
79
78
  // Infrastructure adapters
80
79
  const validatorIdRegistryAdapter = new ValidatorIdRegistryAdapter();
81
80
  const presetConfigAdapter = new PresetConfigAdapter();
@@ -118,25 +117,15 @@ export function buildCiGovernance(
118
117
  const fileScanner = new GlobFileScannerAdapter(baseDir);
119
118
  const fileHasher = new FileSystemSha1HasherAdapter(baseDir);
120
119
  const baselineRepository = new BaselineJsonRepositoryAdapter(baseDir);
121
- const createBaselineUseCase = new CreateBaselineUseCase(
122
- fileScanner,
123
- fileHasher,
124
- baselineRepository,
125
- );
120
+ const createBaselineUseCase = new CreateBaselineUseCase(fileScanner, fileHasher, baselineRepository);
126
121
 
127
122
  // Scaffold design adapters & use case (ISSUE-007 Wave 4)
128
123
  const templateRepository = new FileSystemTemplateRepositoryAdapter(harnessRoot);
129
124
  const designDocWriter = new FileSystemDesignDocWriterAdapter(baseDir);
130
- const scaffoldDesignUseCase = new ScaffoldDesignUseCase(
131
- templateRepository,
132
- designDocWriter,
133
- );
125
+ const scaffoldDesignUseCase = new ScaffoldDesignUseCase(templateRepository, designDocWriter);
134
126
 
135
127
  // Handlers
136
- const generateCiTemplateHandler = new GenerateCiTemplateHandler(
137
- generateCiTemplateUseCase,
138
- renderCiTemplateUseCase,
139
- );
128
+ const generateCiTemplateHandler = new GenerateCiTemplateHandler(generateCiTemplateUseCase, renderCiTemplateUseCase);
140
129
  const migrateAgentsMdHandler = new MigrateAgentsMdHandler(migrateAgentsMdUseCase);
141
130
  const refreshAgentContextHandler = new RefreshAgentContextHandler(refreshAgentContextUseCase);
142
131
  const refreshClaudeMdHandler = new RefreshClaudeMdHandler(refreshClaudeMdUseCase);
@@ -145,6 +134,19 @@ export function buildCiGovernance(
145
134
  const createBaselineHandler = new CreateBaselineHandler(createBaselineUseCase);
146
135
  const scaffoldDesignHandler = new ScaffoldDesignHandler(scaffoldDesignUseCase);
147
136
 
137
+ // Integrity pin adapters & handler (WI-254 / ADR-030 §Decision.3.①)
138
+ const integrityChecker = new IntegrityChecker();
139
+ const sha256Hasher = new FileSystemSha256HasherAdapter(baseDir);
140
+ const integrityManifestRepository = new IntegrityManifestJsonRepositoryAdapter(baseDir);
141
+ const pinIntegrityUseCase = new PinIntegrityUseCase(fileScanner, sha256Hasher, integrityManifestRepository);
142
+ const verifyIntegrityUseCase = new VerifyIntegrityUseCase(
143
+ fileScanner,
144
+ sha256Hasher,
145
+ integrityManifestRepository,
146
+ integrityChecker,
147
+ );
148
+ const integrityHandler = new IntegrityHandler(pinIntegrityUseCase, verifyIntegrityUseCase);
149
+
148
150
  return {
149
151
  generateCiTemplateHandler,
150
152
  migrateAgentsMdHandler,
@@ -161,5 +163,6 @@ export function buildCiGovernance(
161
163
  validatePointersUseCase,
162
164
  createBaselineUseCase,
163
165
  scaffoldDesignUseCase,
166
+ integrityHandler,
164
167
  };
165
168
  }
@@ -0,0 +1,14 @@
1
+ // @unit ci-governance
2
+ // @layer domain
3
+
4
+ import type { IntegrityManifest } from "../value-objects/integrity-manifest.js";
5
+
6
+ /**
7
+ * phasegate.integrity.json の読み書きポート。
8
+ */
9
+ export interface IntegrityManifestRepositoryPort {
10
+ getPath(): string;
11
+ exists(): Promise<boolean>;
12
+ load(): Promise<IntegrityManifest | null>;
13
+ save(manifest: IntegrityManifest): Promise<string>;
14
+ }
@@ -0,0 +1,10 @@
1
+ // @unit ci-governance
2
+ // @layer domain
3
+
4
+ /**
5
+ * ファイルの SHA-256 digest(64 桁小文字 hex)を返すポート。
6
+ * 既存の sha1 用 FileHasherPort とは別ポート(アルゴリズムが異なるため混同回避)。
7
+ */
8
+ export interface Sha256HasherPort {
9
+ hashFile(relativePath: string): Promise<string>;
10
+ }
@@ -0,0 +1,42 @@
1
+ // @unit ci-governance
2
+ // @layer domain
3
+
4
+ import type { IntegrityDrift } from "../value-objects/integrity-drift.js";
5
+ import type { IntegrityManifest } from "../value-objects/integrity-manifest.js";
6
+
7
+ /** manifest 欠落時に返す drift の path(manifest ファイルの相対パス) */
8
+ export const INTEGRITY_MANIFEST_PATH = "phasegate.integrity.json";
9
+
10
+ /**
11
+ * pin された manifest と、現在の再計算結果 (actual) を突き合わせて drift を列挙する
12
+ * 純ドメインサービス(状態なし・I/O なし)。ADR-030 §Decision.3.①。
13
+ */
14
+ export class IntegrityChecker {
15
+ computeDrifts(manifest: IntegrityManifest | null, actual: ReadonlyMap<string, string>): IntegrityDrift[] {
16
+ if (manifest === null) {
17
+ return [{ path: INTEGRITY_MANIFEST_PATH, kind: "manifest-absent" }];
18
+ }
19
+
20
+ const drifts: IntegrityDrift[] = [];
21
+
22
+ // manifest の各 path: actual に無ければ missing、不一致なら mismatch
23
+ for (const [path, expected] of manifest.sortedEntries()) {
24
+ const actualDigest = actual.get(path);
25
+ if (actualDigest === undefined) {
26
+ drifts.push({ path, kind: "missing" });
27
+ } else if (actualDigest !== expected) {
28
+ drifts.push({ path, kind: "mismatch" });
29
+ }
30
+ }
31
+
32
+ // actual の各 path: manifest に無ければ added
33
+ for (const path of actual.keys()) {
34
+ if (manifest.digestOf(path) === undefined) {
35
+ drifts.push({ path, kind: "added" });
36
+ }
37
+ }
38
+
39
+ // path 昇順で決定的に返す
40
+ return drifts.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
41
+ }
42
+ }
@@ -0,0 +1,16 @@
1
+ // @unit ci-governance
2
+ // @layer domain
3
+
4
+ /**
5
+ * verify で検出した整合性差分の種別。
6
+ * - mismatch: digest 不一致
7
+ * - added: 実在するが manifest に無い
8
+ * - missing: manifest にあるが実在しない
9
+ * - manifest-absent: manifest ファイル自体が欠落
10
+ */
11
+ export type IntegrityDriftKind = "mismatch" | "added" | "missing" | "manifest-absent";
12
+
13
+ export interface IntegrityDrift {
14
+ readonly path: string;
15
+ readonly kind: IntegrityDriftKind;
16
+ }
@@ -0,0 +1,49 @@
1
+ // @unit ci-governance
2
+ // @layer domain
3
+
4
+ const SHA256_HEX_RE = /^[0-9a-f]{64}$/;
5
+
6
+ export interface IntegrityManifestProps {
7
+ readonly files: ReadonlyMap<string, string>;
8
+ }
9
+
10
+ /**
11
+ * 指示搭載ファイル群の SHA-256 pin を表す不変 VO(pin 集約ルート)。
12
+ * version は 1 固定、algorithm は 'sha256' 固定(ADR-030 §Decision.3.①)。
13
+ */
14
+ export class IntegrityManifest {
15
+ readonly version = 1 as const;
16
+ readonly algorithm = "sha256" as const;
17
+ private readonly files: ReadonlyMap<string, string>;
18
+
19
+ private constructor(files: ReadonlyMap<string, string>) {
20
+ this.files = new Map(files);
21
+ Object.freeze(this);
22
+ }
23
+
24
+ static create(props: IntegrityManifestProps): IntegrityManifest {
25
+ for (const [path, digest] of props.files) {
26
+ if (path.length === 0) {
27
+ throw new Error("IntegrityManifest: path must not be empty");
28
+ }
29
+ if (!SHA256_HEX_RE.test(digest)) {
30
+ throw new Error(`IntegrityManifest: digest must be 64 lowercase hex chars, got: ${digest} (path: ${path})`);
31
+ }
32
+ }
33
+ return new IntegrityManifest(props.files);
34
+ }
35
+
36
+ /** path 昇順の [path, digest] 一覧(決定的) */
37
+ sortedEntries(): Array<[string, string]> {
38
+ return [...this.files.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
39
+ }
40
+
41
+ /** path 昇順の path 一覧 */
42
+ paths(): readonly string[] {
43
+ return this.sortedEntries().map(([p]) => p);
44
+ }
45
+
46
+ digestOf(path: string): string | undefined {
47
+ return this.files.get(path);
48
+ }
49
+ }
@@ -0,0 +1,40 @@
1
+ // @unit ci-governance
2
+ // @layer domain
3
+
4
+ export interface IntegrityTargetProps {
5
+ readonly include: readonly string[];
6
+ readonly exclude: readonly string[];
7
+ }
8
+
9
+ /**
10
+ * 整合性 pin の対象ファイル集合を表す不変 VO(include / exclude glob)。
11
+ * v1 固定の既定対象は ADR-030 §Decision.3.① の指示搭載ファイル群。
12
+ */
13
+ export class IntegrityTarget {
14
+ readonly include: readonly string[];
15
+ readonly exclude: readonly string[];
16
+
17
+ private constructor(props: IntegrityTargetProps) {
18
+ this.include = Object.freeze([...props.include]);
19
+ this.exclude = Object.freeze([...props.exclude]);
20
+ Object.freeze(this);
21
+ }
22
+
23
+ static create(props: IntegrityTargetProps): IntegrityTarget {
24
+ return new IntegrityTarget(props);
25
+ }
26
+
27
+ /** v1 固定の既定 pin 対象(指示搭載ファイル群) */
28
+ static defaultTargets(): IntegrityTarget {
29
+ return new IntegrityTarget({
30
+ include: [
31
+ "skills/*/SKILL.md",
32
+ ".claude/settings.json",
33
+ ".claude/scripts/*.sh",
34
+ ".husky/*",
35
+ "docs/templates/agent-context/**",
36
+ ],
37
+ exclude: [],
38
+ });
39
+ }
40
+ }
@@ -0,0 +1,17 @@
1
+ // @unit ci-governance
2
+ // @layer infrastructure
3
+
4
+ import * as crypto from "node:crypto";
5
+ import * as fs from "node:fs/promises";
6
+ import * as path from "node:path";
7
+ import type { Sha256HasherPort } from "../../domain/ports/sha256-hasher-port.js";
8
+
9
+ export class FileSystemSha256HasherAdapter implements Sha256HasherPort {
10
+ constructor(private readonly baseDir: string) {}
11
+
12
+ async hashFile(relativePath: string): Promise<string> {
13
+ const absPath = path.isAbsolute(relativePath) ? relativePath : path.join(this.baseDir, relativePath);
14
+ const content = await fs.readFile(absPath);
15
+ return crypto.createHash("sha256").update(content).digest("hex");
16
+ }
17
+ }
@@ -1,81 +1,15 @@
1
1
  // @unit ci-governance
2
2
  // @layer infrastructure
3
+ // @work-item-id WI-250
3
4
 
5
+ import { KNOWN_HARNESS_COMMANDS } from '../../../harness-api/domain/value-objects/known-harness-commands.js';
4
6
  import type { CommandExistencePort } from '../../domain/ports/command-existence-port.js';
5
7
 
6
- const KNOWN_HARNESS_COMMANDS: readonly string[] = [
7
- 'baseline',
8
- 'bypass:audit',
9
- 'check-change-category',
10
- 'check-phase-gate',
11
- 'ci-check',
12
- 'ci:auto-refresh-agent-context',
13
- 'ci:check-repetition',
14
- 'ci:generate-template',
15
- 'ci:migrate-agents-md',
16
- 'commit-msg',
17
- 'complete-check',
18
- 'config:plan',
19
- 'delegate-sonnet',
20
- 'disable-feature',
21
- 'doctor',
22
- 'emit-agent-rules',
23
- 'enable-feature',
24
- 'hook',
25
- 'init',
26
- 'install',
27
- 'lint',
28
- 'list-adrs',
29
- 'list-errors',
30
- 'list-features',
31
- 'migrate',
32
- 'p2:check-agent-context',
33
- 'p2:check-freshness',
34
- 'p2:check-initial-creation',
35
- 'p2:generate-e2e-template',
36
- 'p2:validate-pointers',
37
- 'phasegate:attest',
38
- 'phasegate:check-phase',
39
- 'phasegate:check-ready',
40
- 'phasegate:ci-check',
41
- 'phasegate:complete-check',
42
- 'phasegate:detect-drift',
43
- 'phasegate:generate-matrix',
44
- 'phasegate:impact-analysis',
45
- 'phasegate:lint',
46
- 'phasegate:status',
47
- 'phasegate:verify-attestation',
48
- 'pre-commit',
49
- 'reconcile',
50
- 'refresh-claude-md',
51
- 'regression:analyze-migration',
52
- 'regression:configure-ci-gate',
53
- 'regression:migrate-v0-tests',
54
- 'regression:run-agent-guard',
55
- 'regression:run-gng-gate',
56
- 'regression:run-k-requirements',
57
- 'regression:run-k14-k15',
58
- 'render-errors',
59
- 'scaffold-design',
60
- 'scaffold-wi',
61
- 'session',
62
- 'setup:agent',
63
- 'skill:apply-cascade-update',
64
- 'skill:check-coverage',
65
- 'skill:collect-lessons',
66
- 'skill:execute-tdd-cycle',
67
- 'skill:validate-structure',
68
- 'skills',
69
- 'status',
70
- 'uninstall',
71
- 'update-skills',
72
- 'validate',
73
- 'validate-adr',
74
- 'validate-fix',
75
- 'validate-metadata',
76
- 'work-items:status',
77
- ];
78
-
8
+ /**
9
+ * WI-250: 既知コマンドのデフォルトは harness-api domain の canonical 定数
10
+ * `KNOWN_HARNESS_COMMANDS` を配線する(infra ローカルの重複定義は持たない)。
11
+ * main.ts dispatch との乖離検出は harness-api 側の conformance テストが担う。
12
+ */
79
13
  export class HarnessApiCommandExistenceAdapter implements CommandExistencePort {
80
14
  private readonly knownCommands: Set<string>;
81
15
 
@@ -0,0 +1,83 @@
1
+ // @unit ci-governance
2
+ // @layer infrastructure
3
+
4
+ import * as fs from "node:fs/promises";
5
+ import * as path from "node:path";
6
+ import type { IntegrityManifestRepositoryPort } from "../../domain/ports/integrity-manifest-repository-port.js";
7
+ import { IntegrityManifest } from "../../domain/value-objects/integrity-manifest.js";
8
+
9
+ interface IntegrityJsonV1 {
10
+ readonly version: 1;
11
+ readonly algorithm: "sha256";
12
+ readonly files: Readonly<Record<string, string>>;
13
+ }
14
+
15
+ /**
16
+ * phasegate.integrity.json(ルート)の読み書きアダプタ。
17
+ * files は path 昇順キーで決定的に書き出す。
18
+ */
19
+ export class IntegrityManifestJsonRepositoryAdapter implements IntegrityManifestRepositoryPort {
20
+ private readonly filePath: string;
21
+
22
+ constructor(baseDir: string, relativePath = "phasegate.integrity.json") {
23
+ this.filePath = path.isAbsolute(relativePath) ? relativePath : path.join(baseDir, relativePath);
24
+ }
25
+
26
+ getPath(): string {
27
+ return this.filePath;
28
+ }
29
+
30
+ async exists(): Promise<boolean> {
31
+ try {
32
+ await fs.access(this.filePath);
33
+ return true;
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+
39
+ async save(manifest: IntegrityManifest): Promise<string> {
40
+ const files: Record<string, string> = {};
41
+ for (const [p, digest] of manifest.sortedEntries()) {
42
+ files[p] = digest;
43
+ }
44
+ const data: IntegrityJsonV1 = {
45
+ version: 1,
46
+ algorithm: "sha256",
47
+ files,
48
+ };
49
+ await fs.mkdir(path.dirname(this.filePath), { recursive: true });
50
+ await fs.writeFile(this.filePath, `${JSON.stringify(data, null, 2)}\n`, "utf-8");
51
+ return this.filePath;
52
+ }
53
+
54
+ async load(): Promise<IntegrityManifest | null> {
55
+ let content: string;
56
+ try {
57
+ content = await fs.readFile(this.filePath, "utf-8");
58
+ } catch (err) {
59
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") return null;
60
+ throw err;
61
+ }
62
+
63
+ let parsed: IntegrityJsonV1;
64
+ try {
65
+ parsed = JSON.parse(content) as IntegrityJsonV1;
66
+ } catch (err) {
67
+ throw new Error(`Invalid integrity JSON at ${this.filePath}: ${String(err)}`);
68
+ }
69
+
70
+ if (
71
+ !parsed ||
72
+ parsed.version !== 1 ||
73
+ parsed.algorithm !== "sha256" ||
74
+ typeof parsed.files !== "object" ||
75
+ parsed.files === null
76
+ ) {
77
+ throw new Error(`Invalid integrity JSON schema at ${this.filePath}`);
78
+ }
79
+
80
+ const files = new Map<string, string>(Object.entries(parsed.files));
81
+ return IntegrityManifest.create({ files });
82
+ }
83
+ }
@@ -0,0 +1,76 @@
1
+ // @unit ci-governance
2
+ // @layer presentation
3
+
4
+ import type { PinIntegrityUseCase } from "../../application/usecases/pin-integrity-usecase.js";
5
+ import type { VerifyIntegrityUseCase } from "../../application/usecases/verify-integrity-usecase.js";
6
+ import type { IntegrityDriftKind } from "../../domain/value-objects/integrity-drift.js";
7
+
8
+ export interface IntegrityPinArgs {
9
+ readonly dryRun?: boolean;
10
+ readonly format?: "human" | "json";
11
+ }
12
+
13
+ export interface IntegrityVerifyArgs {
14
+ readonly format?: "human" | "json";
15
+ }
16
+
17
+ export interface IntegrityHandlerResult {
18
+ readonly exitCode: number;
19
+ readonly output: string;
20
+ }
21
+
22
+ const DRIFT_LABEL: Record<IntegrityDriftKind, string> = {
23
+ mismatch: "MISMATCH (digest 不一致)",
24
+ added: "ADDED (manifest 未登録)",
25
+ missing: "MISSING (実ファイル欠落)",
26
+ "manifest-absent": "MANIFEST-ABSENT (manifest 欠落)",
27
+ };
28
+
29
+ export class IntegrityHandler {
30
+ constructor(
31
+ private readonly pinUseCase: PinIntegrityUseCase,
32
+ private readonly verifyUseCase: VerifyIntegrityUseCase,
33
+ ) {}
34
+
35
+ async pin(args: IntegrityPinArgs): Promise<IntegrityHandlerResult> {
36
+ const format = args.format ?? "human";
37
+ const result = await this.pinUseCase.execute({ dryRun: args.dryRun });
38
+
39
+ if (format === "json") {
40
+ return { exitCode: 0, output: JSON.stringify(result, null, 2) };
41
+ }
42
+
43
+ const header = result.dryRun
44
+ ? `[dry run] integrity manifest を生成しました(保存先想定: ${result.savedPath})`
45
+ : `integrity manifest を保存しました: ${result.savedPath}`;
46
+ return {
47
+ exitCode: 0,
48
+ output: [header, `エントリ数: ${result.entryCount}`].join("\n"),
49
+ };
50
+ }
51
+
52
+ async verify(args: IntegrityVerifyArgs): Promise<IntegrityHandlerResult> {
53
+ const format = args.format ?? "human";
54
+ const result = await this.verifyUseCase.execute();
55
+ const exitCode = result.ok ? 0 : 2;
56
+
57
+ if (format === "json") {
58
+ return { exitCode, output: JSON.stringify(result, null, 2) };
59
+ }
60
+
61
+ if (result.ok) {
62
+ return {
63
+ exitCode: 0,
64
+ output: `integrity 照合 OK: drift はありません(${result.manifestPath})`,
65
+ };
66
+ }
67
+
68
+ const lines = [
69
+ `integrity drift を ${result.drifts.length} 件検出しました(${result.manifestPath}):`,
70
+ ...result.drifts.map((d) => `- [${DRIFT_LABEL[d.kind]}] ${d.path}`),
71
+ "",
72
+ "意図的な変更であれば `phasegate integrity:pin` で manifest を更新してください。",
73
+ ];
74
+ return { exitCode: 2, output: lines.join("\n") };
75
+ }
76
+ }