leerness 1.36.183 → 1.36.185
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 +8 -0
- package/LICENSE +21 -21
- package/README.ko.md +15 -8
- package/README.md +25 -7
- package/SECURITY.md +66 -66
- package/bin/leerness.js +1184 -206
- package/docs/role-agent-routing-v2.md +411 -0
- package/lib/agents.js +1236 -100
- package/lib/diagnostics.js +184 -184
- package/lib/file-leases.js +845 -0
- package/lib/health.js +350 -350
- package/lib/io.js +152 -1
- package/lib/mcp-tools.js +121 -104
- package/lib/pure-utils.js +52 -4
- package/lib/referee.js +270 -270
- package/lib/review-request.js +339 -339
- package/lib/role-agent-schema.js +1179 -0
- package/lib/role-catalog.js +31 -1
- package/lib/role-fallback.js +1611 -0
- package/lib/role-store.js +730 -0
- package/lib/routing.js +109 -21
- package/lib/session-close.js +49 -1
- package/package.json +10 -5
- package/scripts/command-flags-probe.js +40 -3
- package/scripts/e2e.js +121 -44
- package/scripts/file-lease-probe.js +1139 -0
- package/scripts/i18n-next-cluster-probe.js +436 -5
- package/scripts/installed-cleanroom-probe.js +39 -12
- package/scripts/lint.js +73 -73
- package/scripts/provider-capacity-probe.js +425 -0
- package/scripts/release-runtime-probe.js +16 -1
- package/scripts/role-agent-schema-probe.js +834 -0
- package/scripts/role-fallback-probe.js +2010 -0
- package/scripts/role-store-loader-probe.js +882 -0
package/lib/pure-utils.js
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// Model identifiers reach argv surfaces. Keep the shell-inert predicate in this
|
|
4
|
+
// side-effect-free module so persisted schemas and runtime dispatch cannot drift.
|
|
5
|
+
const SAFE_MODEL_IDENTIFIER_RE = /^[A-Za-z0-9][A-Za-z0-9._:/@+-]{0,199}$/;
|
|
6
|
+
function isValidModelIdentifier(value) {
|
|
7
|
+
const raw = String(value == null ? '' : value);
|
|
8
|
+
const visible = stripDefaultIgnorables(raw, { nfkc: true, visibleIdentity: true })
|
|
9
|
+
.replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g, '')
|
|
10
|
+
.trim();
|
|
11
|
+
return raw === visible && SAFE_MODEL_IDENTIFIER_RE.test(visible);
|
|
12
|
+
}
|
|
13
|
+
|
|
3
14
|
// 1.9.274 (UR-0025 1단계, GPT-5.5 리뷰): bin/harness.js 단일 대형 파일 모듈 분리 — 점진적·비파괴 시작.
|
|
4
15
|
// 여기에는 harness 내부 상태/다른 함수에 의존하지 않는 "순수 함수"만 추출한다 (부작용 0, 단위 테스트 대상).
|
|
5
16
|
// harness.js 는 이 모듈을 require 해 동일 이름으로 사용한다. 동작 동일 — selftest 가 7종 모두 검증.
|
|
6
17
|
|
|
7
18
|
// 보안: 환경변수 키가 시크릿(TOKEN/SECRET/PASSWORD/API_KEY/PRIVATE)인지 판별.
|
|
8
19
|
function _isSecretKey(k) {
|
|
9
|
-
|
|
20
|
+
// Environment keys are identifiers, not display text. Classify their
|
|
21
|
+
// compatibility spelling so full-width and Default-Ignorable variants cannot
|
|
22
|
+
// pass an ASCII allow-list and reach a child process with their secret value.
|
|
23
|
+
const canonical = stripDefaultIgnorables(k, { nfkc: true, visibleIdentity: true });
|
|
24
|
+
return /TOKEN|SECRET|PASSWORD|API_KEY|PRIVATE/i.test(canonical);
|
|
10
25
|
}
|
|
11
26
|
|
|
12
27
|
// 표시용 자유 텍스트에서 시크릿 값을 가린다 — 키 이름은 남겨 맥락을 유지하고 **값만** 지운다.
|
|
@@ -263,13 +278,19 @@ function _tierRank(t) { const i = PERMISSION_TIERS.indexOf(String(t || '')); ret
|
|
|
263
278
|
// 명령/capability → 요구 등급 (순수 매핑)
|
|
264
279
|
function _requiredTier(cmd) {
|
|
265
280
|
const c = String(cmd || '').toLowerCase();
|
|
281
|
+
// Lease operands are arbitrary file names. Classify the parsed command/action
|
|
282
|
+
// prefix before scanning generic keywords so a path such as
|
|
283
|
+
// `dir/release publish/report.js` cannot turn a read-only check into publish.
|
|
284
|
+
const leaseCommand = c.match(/^\s*(?:(?:npx(?:\s+(?:-y|--yes))?\s+)?leerness(?:@[^\s]+)?\s+)?lease\s+(list|check|acquire|release)(?:\s|$)/);
|
|
285
|
+
if (leaseCommand && (leaseCommand[1] === 'list' || leaseCommand[1] === 'check')) return 'read-only';
|
|
286
|
+
if (leaseCommand) return 'safe-write';
|
|
266
287
|
if (/release\s+publish|npm\s+publish|\bpublish\b/.test(c)) return 'publish';
|
|
267
288
|
if (/\bweb\b/.test(c)) return 'network';
|
|
268
289
|
if (/git\s+push|sync-main/.test(c)) return 'git-write';
|
|
269
290
|
if (/multi\s+--execute|dispatch\s+--write|--yolo|\bpc\b/.test(c)) return 'shell-write';
|
|
270
291
|
if (/agents\s+(list|quota|bench)|--run-tests/.test(c)) return 'shell-read';
|
|
271
292
|
if (/\binit\b|\badapter\b|update\s+--yes|\bmigrate\b/.test(c)) return 'project-write';
|
|
272
|
-
if (/state\s+(start|record|verify|handoff)|decision|lesson|plan\s+add|task\s+add|rule\s+add/.test(c)) return 'safe-write';
|
|
293
|
+
if (/state\s+(start|record|verify|handoff)|decision|lesson|plan\s+add|task\s+add|rule\s+add|lease\s+(acquire|release)/.test(c)) return 'safe-write';
|
|
273
294
|
return 'read-only';
|
|
274
295
|
}
|
|
275
296
|
function _policyAllows(allowedTier, requiredTier) { return _tierRank(requiredTier) <= _tierRank(allowedTier); }
|
|
@@ -868,10 +889,15 @@ function _renderDecisionsMd(decisions) {
|
|
|
868
889
|
// preamble 의 코드펜스(```)는 single-quote 문자열로 안전 처리 (template literal 충돌 회피)
|
|
869
890
|
const preamble = '# Decisions\n\n## Template (예시 — 실제 결정은 아래 코드블록 밖에 추가)\n\n'
|
|
870
891
|
+ '```md\n### YYYY-MM-DD — Decision 제목\n- Decision:\n- Reason:\n- Alternatives:\n- Impact:\n```\n';
|
|
892
|
+
const field = (name, value) => {
|
|
893
|
+
const safe = _lineSafe(value || '');
|
|
894
|
+
return `- ${name}:${safe ? ' ' + safe : ''}`;
|
|
895
|
+
};
|
|
871
896
|
const body = (decisions || []).map(d => {
|
|
872
897
|
// 1.9.402 (UR-0108): 필드 개행 → 공백(MD projection '### '/'- field:' 라인 위조 블록 주입 차단). canonical JSON 은 raw 유지.
|
|
898
|
+
// 1.36.185: 빈 canonical 필드는 colon 뒤 공백을 만들지 않는다 — decision add 후 git diff --check 위반 차단.
|
|
873
899
|
const head = d.date ? `${_lineSafe(d.date)} — ${_lineSafe(d.title)}` : _lineSafe(d.title);
|
|
874
|
-
return `\n### ${head}\n
|
|
900
|
+
return `\n### ${head}\n${field('Decision', d.decision)}\n${field('Reason', d.reason)}\n${field('Alternatives', d.alternatives)}\n${field('Impact', d.impact)}\n`;
|
|
875
901
|
}).join('');
|
|
876
902
|
return preamble + body;
|
|
877
903
|
}
|
|
@@ -1886,6 +1912,7 @@ module.exports = { _decisionBlocksWithOffset, _blocksWithOffset, _lineOfOffset,
|
|
|
1886
1912
|
_parseImplExports, _briefUnfilled, _planGoalUnfilled, _mdSectionBody, _draftAnchors, _replaceMdSection,
|
|
1887
1913
|
_matchTool, _parsePackageJsonDeps, _parseRequirementsTxt, _buildGlossary, _renderGlossaryMd, GLOSSARY_START, GLOSSARY_END,
|
|
1888
1914
|
_isSecretKey, redactSecrets, hasCredentialMarker, compareVer, parseHarnessVersion,
|
|
1915
|
+
SAFE_MODEL_IDENTIFIER_RE, isValidModelIdentifier,
|
|
1889
1916
|
_isPlaceholderSecret, _looksSecretLike,
|
|
1890
1917
|
_mergeLines, _mergeEnvLines, _mergeReadmeSection, _managedMerge, _splitPreserved, PRESERVED_TAG, _detectOrphanGuards, _parseSkillsValue,
|
|
1891
1918
|
_parseArchiveBlocks, _parseSkillCatalog, _renderTeamsMd, _composeTeamPlan, _teamHandoffReminders, _cadenceAssessment, _teamDeployGate, _renderWorkspaceReferenceGuide, _memorySurface, _renderPulseLine,
|
|
@@ -1949,7 +1976,7 @@ module.exports = { _decisionBlocksWithOffset, _blocksWithOffset, _lineOfOffset,
|
|
|
1949
1976
|
// 1.9.399 (7번째 버그헌트 P1-A, UR-0104): markdown 테이블 셀 안전화(파이프/개행 injection 차단)
|
|
1950
1977
|
_cellSafe, _cellUnescape,
|
|
1951
1978
|
// 1.9.402 (7번째 버그헌트 P1-A 잔여, UR-0108): MD projection 라인 안전화(개행→공백)
|
|
1952
|
-
_lineSafe, _milestoneSurface, _recentChangesSurface, _recentChangesAggregate,
|
|
1979
|
+
_lineSafe, stripDefaultIgnorables, _milestoneSurface, _recentChangesSurface, _recentChangesAggregate,
|
|
1953
1980
|
// 1.9.407 (8번째 버그헌트, UR-0111): --limit 안전 파싱(NaN/음수/0 → 기본값)
|
|
1954
1981
|
_parseLimit,
|
|
1955
1982
|
// 1.9.416 (9th 외부평가, UR-0122): add 류 제목 파싱(flag/경로 break) 단일 출처
|
|
@@ -2234,6 +2261,27 @@ function _cellUnescape(s) { return String(s == null ? '' : s).replace(/\\\|/g, '
|
|
|
2234
2261
|
// ESC/CSI·VT·FF·NEL도 제거해 진단 출력과 MD projection에서 가짜 줄뿐 아니라 화면 지우기/커서 이동도 차단한다.
|
|
2235
2262
|
function _lineSafe(s) { return String(s == null ? '' : s).replace(/\r\n|[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g, ' '); }
|
|
2236
2263
|
|
|
2264
|
+
// Security-sensitive comparisons must share one Unicode policy. A hand-maintained
|
|
2265
|
+
// list had diverged between routing and provenance and missed Shorthand Format
|
|
2266
|
+
// Controls (U+1BCA0..U+1BCAF). Prefer the Unicode binary property exposed by
|
|
2267
|
+
// supported Node runtimes, with a conservative fallback for older engines.
|
|
2268
|
+
let _defaultIgnorableRe;
|
|
2269
|
+
try { _defaultIgnorableRe = new RegExp('\\p{Default_Ignorable_Code_Point}', 'gu'); }
|
|
2270
|
+
catch {
|
|
2271
|
+
_defaultIgnorableRe = /[\u00ad\u034f\u061c\u115f-\u1160\u17b4-\u17b5\u180b-\u180f\u200b-\u200f\u202a-\u202e\u2060-\u206f\u3164\ufe00-\ufe0f\ufeff\uffa0\u{1bca0}-\u{1bcaf}\u{1d173}-\u{1d17a}\u{e0000}-\u{e0fff}]/gu;
|
|
2272
|
+
}
|
|
2273
|
+
function stripDefaultIgnorables(value, options = {}) {
|
|
2274
|
+
let text = String(value == null ? '' : value);
|
|
2275
|
+
if (options.nfkc === true) {
|
|
2276
|
+
try { text = text.normalize('NFKC'); } catch {}
|
|
2277
|
+
}
|
|
2278
|
+
text = text.replace(_defaultIgnorableRe, '');
|
|
2279
|
+
// Braille blank is visible-space spoofing rather than a Unicode default
|
|
2280
|
+
// ignorable. Identity/approval callers opt into removing it as well.
|
|
2281
|
+
if (options.visibleIdentity === true) text = text.replace(/\u2800/g, '');
|
|
2282
|
+
return text;
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2237
2285
|
// Git-backed aggregate surfaces must not turn "could not measure" into a
|
|
2238
2286
|
// believable zero. Handoff, health, and session-close share this projection.
|
|
2239
2287
|
function _milestoneSurface(value) {
|