@cheongfish/bouncer 0.0.0-stage → 0.1.13

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 (156) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +31 -2
  3. package/bin/bouncer +26 -0
  4. package/bin/bouncer-root +25 -0
  5. package/bin/build-stamp.js +78 -0
  6. package/bin/register-path.js +71 -0
  7. package/dist/.build-stamp +1 -0
  8. package/dist/approval-snapshot.js +99 -0
  9. package/dist/bouncer-root.js +80 -0
  10. package/dist/canonical-json.js +37 -0
  11. package/dist/cli-commit-guard-command.js +74 -0
  12. package/dist/cli-config-command.js +47 -0
  13. package/dist/cli-context-review-command.js +273 -0
  14. package/dist/cli-current-command.js +374 -0
  15. package/dist/cli-dispatch-command.js +119 -0
  16. package/dist/cli-doc-commands.js +352 -0
  17. package/dist/cli-epic-command.js +130 -0
  18. package/dist/cli-flags.js +50 -0
  19. package/dist/cli-git-commands.js +931 -0
  20. package/dist/cli-intent-command.js +491 -0
  21. package/dist/cli-metrics-command.js +134 -0
  22. package/dist/cli-ontology-command.js +109 -0
  23. package/dist/cli-project-commands.js +580 -0
  24. package/dist/cli-retro-command.js +138 -0
  25. package/dist/cli-review-command.js +251 -0
  26. package/dist/cli-review-dispatch-command.js +338 -0
  27. package/dist/cli-usage.js +72 -0
  28. package/dist/cli.js +70 -0
  29. package/dist/codex-agents.js +189 -0
  30. package/dist/command-argv.js +125 -0
  31. package/dist/commit-guard.js +72 -0
  32. package/dist/commit-hook.js +470 -0
  33. package/dist/commit-message.js +77 -0
  34. package/dist/commit-sha.js +107 -0
  35. package/dist/commit.js +519 -0
  36. package/dist/composite-gates.js +129 -0
  37. package/dist/comprehension.js +127 -0
  38. package/dist/config.js +298 -0
  39. package/dist/context-ids.js +288 -0
  40. package/dist/context-migration-rewrite.js +421 -0
  41. package/dist/context-ontology.js +746 -0
  42. package/dist/context-review-contracts.js +2 -0
  43. package/dist/context-review-projection.js +147 -0
  44. package/dist/context-review-state.js +824 -0
  45. package/dist/context-review-store.js +602 -0
  46. package/dist/context-review-transitions.js +412 -0
  47. package/dist/coordinate-advance.js +253 -0
  48. package/dist/coordinate-next.js +998 -0
  49. package/dist/coordinate-output.js +57 -0
  50. package/dist/coordinator-command-contracts.js +2 -0
  51. package/dist/coordinator-commands.js +1908 -0
  52. package/dist/coordinator-fanin.js +1021 -0
  53. package/dist/coordinator-git.js +121 -0
  54. package/dist/coordinator-input.js +37 -0
  55. package/dist/coordinator-ledger.js +381 -0
  56. package/dist/coordinator-prepare.js +1047 -0
  57. package/dist/coordinator-repair.js +1416 -0
  58. package/dist/coordinator-verification.js +42 -0
  59. package/dist/coordinator-worker.js +388 -0
  60. package/dist/coordinator.js +7 -0
  61. package/dist/cross-drive.js +85 -0
  62. package/dist/current.js +1148 -0
  63. package/dist/dag-shape.js +93 -0
  64. package/dist/doc-shape-contracts.js +241 -0
  65. package/dist/drive-metrics.js +691 -0
  66. package/dist/epic-index.js +347 -0
  67. package/dist/epic-registry.js +453 -0
  68. package/dist/execute-prepare.js +262 -0
  69. package/dist/execution-contracts.js +46 -0
  70. package/dist/execution-dag.js +148 -0
  71. package/dist/finalize-checks.js +177 -0
  72. package/dist/finalize-collect.js +343 -0
  73. package/dist/finalize-comprehension.js +116 -0
  74. package/dist/finalize-digest-collect.js +388 -0
  75. package/dist/finalize-digest-model.js +595 -0
  76. package/dist/finalize-digest.js +164 -0
  77. package/dist/finalize-explain.js +176 -0
  78. package/dist/finalize-pr.js +610 -0
  79. package/dist/finalize-release-main.js +652 -0
  80. package/dist/finalize.js +714 -0
  81. package/dist/frontmatter.js +22 -0
  82. package/dist/git-ops.js +49 -0
  83. package/dist/graph-exec.js +179 -0
  84. package/dist/graph-scope.js +268 -0
  85. package/dist/graphify.js +995 -0
  86. package/dist/guards.js +82 -0
  87. package/dist/import-git.js +78 -0
  88. package/dist/import-history.js +392 -0
  89. package/dist/import-render.js +59 -0
  90. package/dist/import-types.js +2 -0
  91. package/dist/init.js +571 -0
  92. package/dist/intent-bundle.js +936 -0
  93. package/dist/intent-provenance.js +927 -0
  94. package/dist/layout.js +131 -0
  95. package/dist/lease.js +145 -0
  96. package/dist/legacy-comments.js +71 -0
  97. package/dist/migrate-context-slugs.js +262 -0
  98. package/dist/migrate-merge-legacy-epics.js +392 -0
  99. package/dist/migrate-review-scope.js +144 -0
  100. package/dist/migrate-task-layout.js +194 -0
  101. package/dist/path-overlap.js +38 -0
  102. package/dist/paths.js +45 -0
  103. package/dist/plan-approve.js +218 -0
  104. package/dist/plan-inspect.js +575 -0
  105. package/dist/plan-prerequisites.js +197 -0
  106. package/dist/plan-snapshot-contract.js +98 -0
  107. package/dist/plan-snapshot.js +138 -0
  108. package/dist/plugin-root.js +237 -0
  109. package/dist/posix-path.js +14 -0
  110. package/dist/pre-commit-hook.js +170 -0
  111. package/dist/print-dispatch.js +285 -0
  112. package/dist/render.js +8 -0
  113. package/dist/retention-migration.js +595 -0
  114. package/dist/retro-read.js +259 -0
  115. package/dist/retro-sources.js +318 -0
  116. package/dist/review-dispatch.js +1054 -0
  117. package/dist/review-policy.js +28 -0
  118. package/dist/review-record.js +447 -0
  119. package/dist/review-repair-state.js +52 -0
  120. package/dist/review-snapshot.js +659 -0
  121. package/dist/run-detach.js +165 -0
  122. package/dist/run-preflight.js +368 -0
  123. package/dist/runtime-state.js +1038 -0
  124. package/dist/scaffold.js +533 -0
  125. package/dist/schema.js +294 -0
  126. package/dist/scope.js +1073 -0
  127. package/dist/seed-worktree.js +926 -0
  128. package/dist/session-graph.js +337 -0
  129. package/dist/subagents.js +86 -0
  130. package/dist/symbol-index.js +1390 -0
  131. package/dist/task-brief-hash.js +34 -0
  132. package/dist/task-commits.js +124 -0
  133. package/dist/tasks-docs.js +192 -0
  134. package/dist/templates.js +469 -0
  135. package/dist/time.js +21 -0
  136. package/dist/validate-docs.js +302 -0
  137. package/dist/validate-gates-finalize.js +303 -0
  138. package/dist/validate-gates-plan.js +720 -0
  139. package/dist/validate-gates-run.js +420 -0
  140. package/dist/validate-gates-shared.js +124 -0
  141. package/dist/validate-gates.js +87 -0
  142. package/dist/validate-sections.js +654 -0
  143. package/dist/validate-structural.js +347 -0
  144. package/dist/validate.js +417 -0
  145. package/dist/verification-contracts.js +26 -0
  146. package/dist/verification-evidence.js +233 -0
  147. package/dist/verification-execution.js +163 -0
  148. package/dist/verification-identity.js +413 -0
  149. package/dist/verification-target.js +51 -0
  150. package/dist/verification.js +383 -0
  151. package/dist/windows-npm.js +38 -0
  152. package/package.json +32 -4
  153. package/resources/graphify-compat.json +9 -0
  154. package/vendor/README.md +23 -0
  155. package/vendor/js-yaml.LICENSE +21 -0
  156. package/vendor/js-yaml.js +3022 -0
package/dist/schema.js ADDED
@@ -0,0 +1,294 @@
1
+ 'use strict';
2
+ const OKF_REQUIRED = ['type', 'title', 'description', 'resource', 'tags', 'timestamp'];
3
+ const TYPES = [
4
+ 'bouncer.epic',
5
+ 'bouncer.blueprint',
6
+ 'bouncer.tasks',
7
+ 'bouncer.verification',
8
+ 'bouncer.review',
9
+ 'bouncer.explain',
10
+ 'bouncer.context_review',
11
+ ];
12
+ // epic/blueprint 정본은 접두 없는 \d{3}. 빈 문자열은 S4가 숫자 규칙으로 분기할 힌트.
13
+ // 자식 문서만 KIND- 접두 + \d{3}(예: TASKS-001). 구형 EPIC-/BP- 메타는 validate 정규화로 통과.
14
+ const ID_PREFIX = {
15
+ 'bouncer.epic': '',
16
+ 'bouncer.blueprint': '',
17
+ 'bouncer.tasks': 'TASKS-',
18
+ 'bouncer.verification': 'VERIFY-',
19
+ 'bouncer.review': 'REVIEW-',
20
+ 'bouncer.explain': 'EXPLAIN-',
21
+ 'bouncer.context_review': 'CTXREVIEW-',
22
+ };
23
+ const STATUS_ENUM = {
24
+ // imported: 히스토리 임포트 문서. 게이트 대상이 아니며 validateBlueprint가 S18로 거절.
25
+ 'bouncer.epic': ['draft', 'approved', 'closed', 'imported'],
26
+ // 'closed'는 finalize --yes가 마감한 blueprint에 찍는 잠금 status.
27
+ // draft → approved 만 있던 어휘에 마감 표시를 추가; superseded는 유지.
28
+ // imported는 임포트 전용 — 작업 포인터/게이트 대상에서 제외(S18).
29
+ 'bouncer.blueprint': ['draft', 'approved', 'superseded', 'closed', 'partial_closed', 'imported'],
30
+ 'bouncer.tasks': ['draft', 'ready', 'in_progress', 'verified', 'verifying', 'integrated'],
31
+ 'bouncer.verification': ['pending', 'passed', 'failed'],
32
+ 'bouncer.review': ['pending', 'requested', 'addressed', 'accepted'],
33
+ 'bouncer.explain': ['draft', 'published'],
34
+ // 계획 문서 정합성 판정. 어휘는 bouncer.review와 같다 — 새 status를 만들지 않는다.
35
+ 'bouncer.context_review': ['pending', 'requested', 'addressed', 'accepted'],
36
+ };
37
+ const KIND_TO_TYPE = {
38
+ epic: 'bouncer.epic',
39
+ blueprint: 'bouncer.blueprint',
40
+ tasks: 'bouncer.tasks',
41
+ verification: 'bouncer.verification',
42
+ review: 'bouncer.review',
43
+ explain: 'bouncer.explain',
44
+ context_review: 'bouncer.context_review',
45
+ };
46
+ const LEGACY_GUIDANCE = 'Legacy SDD format detected. Re-initialize with /bouncer-init (no automatic migration).';
47
+ // 번들 루트(.bouncer/context/index.md) 전용. OKF okf_version과 자리를 나누되
48
+ // Bouncer 문서 스키마 약속은 여기 한 곳에만 둔다 — 문서마다 올리면 드리프트한다.
49
+ // 값은 "0.1". 1.0 승격은 epic 029 소관이며 이 상수를 임의로 올리지 않는다.
50
+ const BOUNCER_SCHEMA_VERSION = '0.1';
51
+ // blueprint bouncer.scale 허용값. 소비자는 scale === 'light'만 보고,
52
+ // 부재·'full'은 모두 일반 경로로 읽힌다(full 분기 금지).
53
+ const SCALE_ENUM = ['light', 'full'];
54
+ // scaffold가 blueprint에 쓰는 기본값. 경량 선언은 plan이 이 값을 light로 바꾼다.
55
+ const DEFAULT_SCALE = 'full';
56
+ // blueprint `bouncer.verification_policy` 허용값은 하나뿐이다. 구현 task는 자기
57
+ // 전용 `bouncer.verify`를 갖고, 전체 검증은 마지막 verification node에서 한 번
58
+ // 돈다. 키 부재는 정책 이전 계획(legacy)이며 config.verify 폴백과 기존 topology를
59
+ // 그대로 유지한다 — 소급 migration이 없으므로 부재를 새 규칙으로 읽지 않는다.
60
+ const VERIFICATION_POLICY_ENUM = ['scoped_terminal'];
61
+ /**
62
+ * blueprint `bouncer` 블록의 검증 정책을 해석한다.
63
+ * 소비자(plan gate·verify 명령 선택·coordinator)가 같은 판정을 쓰도록 한 곳에 둔다.
64
+ *
65
+ * @param {unknown} bouncer - blueprint index.md의 bouncer 객체
66
+ * @returns {'scoped_terminal' | 'legacy' | null} 키 부재·비객체는 legacy,
67
+ * 허용값은 그대로, 허용 밖의 값(null 포함)은 null — 호출자가 명시적으로 거절한다
68
+ */
69
+ function verificationPolicyOf(bouncer) {
70
+ if (bouncer == null || typeof bouncer !== 'object' || Array.isArray(bouncer))
71
+ return 'legacy';
72
+ const rec = bouncer;
73
+ if (!Object.prototype.hasOwnProperty.call(rec, 'verification_policy'))
74
+ return 'legacy';
75
+ const value = rec.verification_policy;
76
+ return VERIFICATION_POLICY_ENUM.includes(value)
77
+ ? value
78
+ : null;
79
+ }
80
+ // blueprint `bouncer.review_contract` 허용값은 하나뿐이다. `brief`는 최종 리뷰가
81
+ // 브리프 이행 판정 관점으로 도는 새 계약이고, 키 부재는 legacy다 — 소급 migration이
82
+ // 없으므로 부재를 새 계약으로 읽지 않는다. plan 구조 해시에는 넣지 않는다.
83
+ const REVIEW_CONTRACT_ENUM = ['brief'];
84
+ // 표식이 허용 밖 값일 때의 거절 상수 정본. 분류기·coordinator·CLI가 같은 reason과 문구를 쓰도록
85
+ // 여기 둔다. CLI는 분류기를 적재하기 전에 거절하므로 review-dispatch가 아니라 schema에서 가져와야 한다.
86
+ const REVIEW_CONTRACT_INVALID_REASON = 'review-contract-invalid';
87
+ const REVIEW_CONTRACT_INVALID_CAUSE = 'review_contract must be brief';
88
+ const REVIEW_CONTRACT_INVALID_MESSAGE = `${REVIEW_CONTRACT_INVALID_REASON}: ${REVIEW_CONTRACT_INVALID_CAUSE}`;
89
+ // 표식(review_contract: brief) blueprint의 plan 리뷰 관점. review-dispatch plan이 내는 값과
90
+ // context-review가 round를 대조하는 기준이 같은 정본을 보도록 여기 둔다. full은 4관점 병렬,
91
+ // light는 verifiability 하나다.
92
+ const PLAN_FULL_PERSPECTIVES = ['consistency', 'scope_minimality', 'verifiability', 'risk_security'];
93
+ const PLAN_LIGHT_PERSPECTIVES = ['verifiability'];
94
+ /**
95
+ * 표식 blueprint의 delta round가 가져야 하는 관점 이름을 검증된 scale로 고른다.
96
+ * full은 4관점을 하나로 합친 `combined`, light는 verifiability 하나다. 관점 개수로 light를
97
+ * 추론하지 않고 호출자가 loadDispatch로 검증한 scale만 받는다 — 잘못된 scale은 loadDispatch가
98
+ * 이미 거절하므로 여기서는 다시 검사하지 않는다.
99
+ *
100
+ * @param {'full' | 'light'} scale - loadDispatch가 검증한 blueprint scale
101
+ * @returns {string} delta round의 단일 관점 이름
102
+ */
103
+ function planDeltaPerspectiveOf(scale) {
104
+ return scale === 'light' ? PLAN_LIGHT_PERSPECTIVES[0] : 'combined';
105
+ }
106
+ /**
107
+ * blueprint `bouncer` 블록의 plan 리뷰 계약 표식을 해석한다. scale을 보지 않는다 —
108
+ * light도 표식을 가질 수 있고(light는 verifiability 한 관점으로 plan 리뷰를 받는다),
109
+ * 크기·task 수로 표식을 추론하지 않기 위해서다.
110
+ *
111
+ * @param {unknown} bouncer - blueprint index.md의 bouncer 객체
112
+ * @returns {'brief' | 'legacy' | null} 키 부재·비객체는 legacy, 허용값은 그대로,
113
+ * 허용 밖의 값(빈 문자열·null 포함)은 null — 호출자가 legacy로 접지 않고 거절한다
114
+ */
115
+ function planReviewContractOf(bouncer) {
116
+ if (bouncer == null || typeof bouncer !== 'object' || Array.isArray(bouncer))
117
+ return 'legacy';
118
+ const rec = bouncer;
119
+ if (!Object.prototype.hasOwnProperty.call(rec, 'review_contract'))
120
+ return 'legacy';
121
+ return REVIEW_CONTRACT_ENUM.includes(rec.review_contract)
122
+ ? rec.review_contract
123
+ : null;
124
+ }
125
+ /**
126
+ * blueprint `bouncer` 블록의 run(최종 리뷰) 쪽 리뷰 계약 표식을 해석한다.
127
+ * 구조 검사(S38)와 review-dispatch가 같은 판정을 쓰도록 한 곳에 둔다.
128
+ * light는 키 값과 관계없이 legacy다: 표식 light의 최종 리뷰는 표식 없는 light와
129
+ * 같아야 하고, 잘못된 값의 거절은 S38이 맡는다. run 쪽 소비자를 건드리지 않고
130
+ * light 처리를 해석기 한 곳에서만 바꾸려는 선택이다.
131
+ *
132
+ * @param {unknown} bouncer - blueprint index.md의 bouncer 객체
133
+ * @returns {'brief' | 'legacy' | null} light는 항상 legacy, 그 밖에는 planReviewContractOf와 같다
134
+ */
135
+ function reviewContractOf(bouncer) {
136
+ if (bouncer != null && typeof bouncer === 'object' && !Array.isArray(bouncer)
137
+ && bouncer.scale === 'light')
138
+ return 'legacy';
139
+ return planReviewContractOf(bouncer);
140
+ }
141
+ // finalize가 읽되 scaffold가 비우면 항상 'feat' 폴백으로 떨어지던 구멍 —
142
+ // blueprint에 기본 commit_type을 써 둔다. 값 검사는 TASKS-001 branch helper가
143
+ // 새 worktree 생성 전에 맡아, coordinator와 다른 소비자가 다른 이름을 만들지 않는다.
144
+ const DEFAULT_COMMIT_TYPE = 'feat';
145
+ // .gitmessage가 허용하는 Conventional Commit 접두를 branch 이름에도 그대로 쓴다.
146
+ // 새 종류를 추가할 때 문서·commit hook·branch helper가 흩어지지 않도록 정본을 둔다.
147
+ const COMMIT_TYPE_ENUM = ['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore'];
148
+ // config.json autonomy 허용값. 소비자는 autonomy === 'interactive'만 보고,
149
+ // 부재·'auto'는 모두 같은 경로로 읽힌다(auto 전용 분기 금지).
150
+ const AUTONOMY_ENUM = ['auto', 'interactive'];
151
+ // init이 새 저장소 config.json에 쓰는 기본값. 키가 없어도 소비자는 auto로 읽는다.
152
+ const DEFAULT_AUTONOMY = 'auto';
153
+ // task dependency 해제 시점. 선행 task가 이 상태에 도달해야 후속이 열린다.
154
+ // 부재는 scaffold 기본값(integrated)과 같이 읽힌다 — 소급 migration 없음.
155
+ const DEPENDENCY_GATE_ENUM = ['integrated'];
156
+ // scaffold가 새 tasks.md에 쓰는 DAG 기본값. 기존 문서의 필드 부재는
157
+ // 같은 의미(의존 없음·순차·integrated)로 읽히므로 호환이 유지된다.
158
+ const DEFAULT_DEPENDS_ON = [];
159
+ const DEFAULT_PARALLEL_SAFE = false;
160
+ const DEFAULT_DEPENDENCY_GATE = 'integrated';
161
+ // exclusive_resources 부재는 빈 배열과 같다. S28과 coordinator 스냅샷이
162
+ // 같은 기본값을 써야 plan 통과 문서가 원장에서 다른 충돌 집합을 갖지 않는다.
163
+ const DEFAULT_EXCLUSIVE_RESOURCES = [];
164
+ // 소문자·숫자로 시작, 이후에 ._- 만. 대문자·공백은 자원 id로 쓰지 않는다 —
165
+ // 호출처마다 trim·lower를 다르게 적용하면 같은 문자열이 충돌/비충돌로 갈라진다.
166
+ const EXCLUSIVE_RESOURCE_ID_RE = /^[a-z0-9][a-z0-9._-]*$/;
167
+ // blueprint `bouncer.prerequisites[].merged_commit` 형식. 선행 blueprint가 실제로 병합된 결과 commit이며
168
+ // squash/rebase에서도 원래 worker SHA가 아니라 병합 결과를 가리킨다. 전체 40자리 소문자만 받아
169
+ // 같은 commit이 약어·대문자로 갈라지지 않게 한다. 키 부재/[]는 선행 없음(기존 계획은 그대로).
170
+ const PREREQUISITE_MERGED_COMMIT_RE = /^[0-9a-f]{40}$/;
171
+ // 키 부재는 기존 task의 commit 의미를 보존한다. 잘못 쓴 문자열은 null로
172
+ // 남겨 구조 검사가 명시적으로 거절하게 한다.
173
+ const EXECUTION_KIND_ENUM = ['commit', 'verification'];
174
+ /**
175
+ * task 메타데이터의 실행 종류를 하위 호환 기본값과 함께 해석한다.
176
+ *
177
+ * @param {unknown} bouncer - tasks.md의 bouncer 객체
178
+ * @returns {'commit' | 'verification' | null} 유효 종류, 잘못된 객체·enum이면 null
179
+ */
180
+ function executionKindOf(bouncer) {
181
+ if (bouncer == null || typeof bouncer !== 'object' || Array.isArray(bouncer))
182
+ return null;
183
+ const value = bouncer.execution_kind;
184
+ if (value === undefined)
185
+ return 'commit';
186
+ return EXECUTION_KIND_ENUM.includes(value)
187
+ ? value
188
+ : null;
189
+ }
190
+ /**
191
+ * task `bouncer.depends_on` 형식만 판정한다.
192
+ * 부재(undefined)는 통과 — 기존 plan을 빈 배열로 읽기 위함.
193
+ * 참조 무결성·자기참조·중복·순환은 검사하지 않는다. G19가 그 판정을 맡는다.
194
+ *
195
+ * @param {unknown} value - 프론트매터의 depends_on 값. 키가 없으면 undefined
196
+ * @returns {boolean} 허용 형태면 true, 그 외(null·비배열·비 TASKS-NNN)면 false
197
+ */
198
+ function isValidDependsOn(value) {
199
+ if (value === undefined)
200
+ return true;
201
+ if (!Array.isArray(value))
202
+ return false;
203
+ return value.every((entry) => typeof entry === 'string' && /^TASKS-\d{3}$/.test(entry));
204
+ }
205
+ /**
206
+ * task `bouncer.exclusive_resources` 형식만 판정한다.
207
+ * 부재(undefined)는 통과 — 기존 plan을 빈 배열로 읽기 위함.
208
+ * 항목 shape·중복만 본다. 자원 의미·교차 문서 충돌은 readyWave가 맡는다.
209
+ *
210
+ * @param {unknown} value - 프론트매터의 exclusive_resources 값. 키가 없으면 undefined
211
+ * @returns {boolean} 허용 형태면 true, 그 외(비배열·빈 문자열·대문자·중복)면 false
212
+ */
213
+ function isValidExclusiveResources(value) {
214
+ if (value === undefined)
215
+ return true;
216
+ if (!Array.isArray(value))
217
+ return false;
218
+ const seen = new Set();
219
+ for (const entry of value) {
220
+ if (typeof entry !== 'string' || !EXCLUSIVE_RESOURCE_ID_RE.test(entry))
221
+ return false;
222
+ if (seen.has(entry))
223
+ return false;
224
+ seen.add(entry);
225
+ }
226
+ return true;
227
+ }
228
+ /**
229
+ * epic·blueprint `bouncer.supersedes` 형식만 판정한다.
230
+ * 부재(undefined)와 빈 배열은 통과 — 기존 문서 소급 없이 신규부터 자리를 쓰기 위함.
231
+ * 참조 무결성(존재·자기참조·순환·중복)은 검사하지 않는다. S27이 이 헬퍼만 본다.
232
+ *
233
+ * @param {unknown} value - 프론트매터의 supersedes 값. 키가 없으면 undefined
234
+ * @returns {boolean} 허용 형태면 true, 그 외(null·비배열·공백/비문자열 원소)면 false
235
+ */
236
+ /**
237
+ * task `bouncer.commit_intent`·`commit_summary` 형식만 판정한다.
238
+ * 부재는 통과 — 옛 tasks.md를 소급하지 않기 위함. 값은 YAML 리스트여야 한다.
239
+ * 스칼라 문자열은 js-yaml이 `commit_intent: 문장`으로 읽어 커밋 직전에야
240
+ * 거절되던 구멍을 여기서 막는다. 줄 수 0–2·원소가 문자열인지만 본다.
241
+ * 한국어 종결형은 `normalizeAuthoredLines`가 커밋 때 판정한다.
242
+ *
243
+ * @param {unknown} value - 프론트매터 값. 키가 없으면 undefined
244
+ * @returns {boolean} 허용 형태면 true, 스칼라·3개 초과·비문자열 원소면 false
245
+ */
246
+ function isValidAuthoredLineList(value) {
247
+ if (value === undefined)
248
+ return true;
249
+ if (!Array.isArray(value) || value.length > 2)
250
+ return false;
251
+ return value.every((entry) => typeof entry === 'string');
252
+ }
253
+ function isValidSupersedes(value) {
254
+ // 키 부재만 허용. null은 "명시적으로 잘못된 값"이라 거절한다(scale 부재 계약과 구분).
255
+ if (value === undefined)
256
+ return true;
257
+ if (!Array.isArray(value))
258
+ return false;
259
+ return value.every((entry) => typeof entry === 'string' && entry.trim() !== '');
260
+ }
261
+ // public-name: legacy-input-detector
262
+ function detectLegacyFormat({ repoRoot, data } = {}) {
263
+ if (repoRoot) {
264
+ const fs = require('node:fs');
265
+ const path = require('node:path');
266
+ if (fs.existsSync(path.join(repoRoot, '.sdd'))) {
267
+ return { legacy: true, reason: LEGACY_GUIDANCE };
268
+ }
269
+ }
270
+ if (data && typeof data === 'object') {
271
+ const rec = data;
272
+ if (Object.prototype.hasOwnProperty.call(rec, 'sdd')) {
273
+ return { legacy: true, reason: LEGACY_GUIDANCE };
274
+ }
275
+ if (typeof rec.type === 'string' && rec.type.startsWith('sdd.')) {
276
+ return { legacy: true, reason: LEGACY_GUIDANCE };
277
+ }
278
+ }
279
+ return { legacy: false };
280
+ }
281
+ module.exports = {
282
+ OKF_REQUIRED, TYPES, ID_PREFIX, STATUS_ENUM, KIND_TO_TYPE,
283
+ LEGACY_GUIDANCE, detectLegacyFormat,
284
+ BOUNCER_SCHEMA_VERSION, SCALE_ENUM, DEFAULT_SCALE, DEFAULT_COMMIT_TYPE, COMMIT_TYPE_ENUM,
285
+ VERIFICATION_POLICY_ENUM, verificationPolicyOf,
286
+ REVIEW_CONTRACT_ENUM, REVIEW_CONTRACT_INVALID_REASON, REVIEW_CONTRACT_INVALID_CAUSE,
287
+ REVIEW_CONTRACT_INVALID_MESSAGE, reviewContractOf, planReviewContractOf,
288
+ PLAN_FULL_PERSPECTIVES, PLAN_LIGHT_PERSPECTIVES, planDeltaPerspectiveOf,
289
+ AUTONOMY_ENUM, DEFAULT_AUTONOMY, isValidSupersedes,
290
+ DEPENDENCY_GATE_ENUM, DEFAULT_DEPENDS_ON, DEFAULT_PARALLEL_SAFE,
291
+ DEFAULT_DEPENDENCY_GATE, DEFAULT_EXCLUSIVE_RESOURCES, isValidDependsOn,
292
+ isValidExclusiveResources, EXCLUSIVE_RESOURCE_ID_RE, isValidAuthoredLineList,
293
+ EXECUTION_KIND_ENUM, executionKindOf, PREREQUISITE_MERGED_COMMIT_RE,
294
+ };