@mrciphersmith/keryx 0.2.69 → 0.2.71
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/dist/cli.js +11136 -4863
- package/docs/README.md +54 -0
- package/docs/requirements/shared-agent-context/README.md +104 -0
- package/package.json +3 -2
- package/src/gdgraph/build-lang.test.ts +10 -3
- package/src/gdgraph/build.ts +54 -9
- package/src/gdgraph/import-kind.test.ts +205 -0
- package/src/gdgraph/query.ts +6 -1
- package/src/gdgraph/types.ts +34 -0
- package/src/gdskills/bundled/rules/core/model-selection.mdc +184 -31
- package/src/gdskills/bundled/rules/core/skills-storage-workflow.mdc +36 -0
- package/src/gdskills/bundled/rules/core/subagent-status-protocol.md +27 -1
- package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.md +2 -1
- package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.opencode.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.zed.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/context-collector/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/context-collector/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/context-collector/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/context-collector/SKILL.opencode.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/context-collector/SKILL.zed.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.opencode.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.zed.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/feature-dev/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/feature-dev/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/feature-dev/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/flow-orchestrator/SKILL.md +159 -20
- package/src/gdskills/bundled/skills/orchestration/issue-analyzer/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/issue-analyzer/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/issue-analyzer/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/issue-analyzer/SKILL.opencode.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/issue-analyzer/SKILL.zed.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/job-documenter/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/job-documenter/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/job-documenter/SKILL.md +2 -1
- package/src/gdskills/bundled/skills/orchestration/job-documenter/SKILL.opencode.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/job-documenter/SKILL.zed.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.codex.md +28 -3
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.cursor.md +28 -3
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +28 -3
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.opencode.md +28 -3
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.zed.md +28 -3
- package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.codex.md +20 -2
- package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.cursor.md +20 -2
- package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.md +22 -3
- package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.opencode.md +20 -2
- package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.zed.md +20 -2
- package/src/gdskills/bundled/skills/planning/autodoc-analyst/SKILL.md +2 -1
- package/src/gdskills/bundled/skills/planning/autodoc-architect/SKILL.md +3 -1
- package/src/gdskills/bundled/skills/planning/autodoc-assembler/SKILL.md +2 -1
- package/src/gdskills/bundled/skills/planning/autodoc-orchestrator/SKILL.md +2 -1
- package/src/gdskills/bundled/skills/planning/autodoc-scanner/SKILL.md +2 -1
- package/src/gdskills/bundled/skills/planning/autodoc-writer/SKILL.md +2 -1
- package/src/gdskills/bundled/skills/planning/brainstorm/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/planning/brainstorm/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/planning/brainstorm/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/planning/consistency-checker/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/planning/consistency-checker/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/planning/consistency-checker/SKILL.md +2 -1
- package/src/gdskills/bundled/skills/planning/docpack-orchestrator/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/planning/docpack-review/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/planning/interview/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/planning/interview/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/planning/interview/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/planning/interviewer/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/planning/interviewer/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/planning/interviewer/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/planning/patterns-researcher/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/planning/patterns-researcher/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/planning/patterns-researcher/SKILL.md +2 -1
- package/src/gdskills/bundled/skills/planning/planner/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/planning/planner/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/planning/planner/SKILL.md +2 -1
- package/src/gdskills/bundled/skills/planning/prd-creator/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/planning/prd-creator/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/planning/prd-creator/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/planning/prd-creator/SKILL.opencode.md +1 -1
- package/src/gdskills/bundled/skills/planning/prd-creator/SKILL.zed.md +1 -1
- package/src/gdskills/bundled/skills/planning/problem-definer/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/planning/problem-definer/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/planning/problem-definer/SKILL.md +2 -1
- package/src/gdskills/bundled/skills/planning/project-discovery/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/planning/project-discovery/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/planning/project-discovery/SKILL.md +2 -1
- package/src/gdskills/bundled/skills/planning/spec-writer/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/planning/spec-writer/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/planning/spec-writer/SKILL.md +2 -1
- package/src/gdskills/bundled/skills/planning/stack-advisor/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/planning/stack-advisor/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/planning/stack-advisor/SKILL.md +2 -1
- package/src/gdskills/bundled/skills/platform/claude-md-management/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/platform/claude-md-management/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/platform/claude-md-management/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/platform/hookify/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/platform/hookify/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/platform/hookify/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/quality/changelog/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/quality/changelog/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/quality/changelog/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/quality/commit/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/quality/commit/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/quality/commit/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/quality/db-migrate/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/quality/db-migrate/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/quality/db-migrate/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/quality/dependency-update/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/quality/dependency-update/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/quality/dependency-update/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/quality/deploy/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/quality/deploy/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/quality/deploy/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/quality/metaproject-security/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/quality/perf-check/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/quality/perf-check/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/quality/perf-check/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/quality/pr/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/quality/pr/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/quality/pr/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/quality/pr-issue-documenter/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/quality/pr-issue-documenter/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/quality/pr-issue-documenter/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/quality/pr-issue-documenter/SKILL.opencode.md +1 -1
- package/src/gdskills/bundled/skills/quality/pr-issue-documenter/SKILL.zed.md +1 -1
- package/src/gdskills/bundled/skills/quality/push/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/quality/push/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/quality/push/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/quality/security-audit/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/quality/security-audit/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/quality/security-audit/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/quality/test-gen/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/quality/test-gen/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/quality/test-gen/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/quality/tests-creator/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/quality/tests-creator/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/quality/tests-creator/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/quality/tests-creator/SKILL.opencode.md +1 -1
- package/src/gdskills/bundled/skills/quality/tests-creator/SKILL.zed.md +1 -1
- package/src/gdskills/bundled/skills/review/code-ai-review/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/review/code-ai-review/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/review/code-ai-review/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/review/code-ai-review/SKILL.opencode.md +1 -1
- package/src/gdskills/bundled/skills/review/code-ai-review/SKILL.zed.md +1 -1
- package/src/gdskills/bundled/skills/review/code-b091-review/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/review/code-b091-review/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/review/code-b091-review/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/review/code-b091-review/SKILL.opencode.md +1 -1
- package/src/gdskills/bundled/skills/review/code-b091-review/SKILL.zed.md +1 -1
- package/src/gdskills/bundled/skills/review/code-mobx-store-review/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/review/code-mobx-store-review/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/review/code-mobx-store-review/SKILL.md +2 -1
- package/src/gdskills/bundled/skills/review/code-mobx-store-review/SKILL.opencode.md +1 -1
- package/src/gdskills/bundled/skills/review/code-mobx-store-review/SKILL.zed.md +1 -1
- package/src/gdskills/bundled/skills/review/code-style-review/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/review/code-style-review/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/review/code-style-review/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/review/code-style-review/SKILL.opencode.md +1 -1
- package/src/gdskills/bundled/skills/review/code-style-review/SKILL.zed.md +1 -1
- package/src/gdskills/bundled/skills/review/review-architecture/SKILL.md +37 -10
- package/src/gdskills/bundled/skills/review/review-backend/SKILL.md +48 -14
- package/src/gdskills/bundled/skills/review/review-clean-code/SKILL.md +49 -12
- package/src/gdskills/bundled/skills/review/review-core-boundaries/SKILL.md +34 -2
- package/src/gdskills/bundled/skills/review/review-flow-graph/SKILL.md +33 -2
- package/src/gdskills/bundled/skills/review/review-frontend/SKILL.md +70 -29
- package/src/gdskills/bundled/skills/review/review-frontend-conventions/SKILL.md +34 -3
- package/src/gdskills/bundled/skills/review/review-highload/SKILL.md +49 -15
- package/src/gdskills/bundled/skills/review/review-logic/SKILL.md +39 -11
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +659 -64
- package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-finding.schema.json +7 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/verification-claim.schema.json +78 -0
- package/src/gdskills/bundled/skills/review/review-performance/SKILL.md +43 -13
- package/src/gdskills/bundled/skills/review/review-pr-feedback/SKILL.md +8 -2
- package/src/gdskills/bundled/skills/review/review-regression/SKILL.md +185 -0
- package/src/gdskills/bundled/skills/review/review-security-code/SKILL.md +44 -13
- package/src/gdskills/bundled/skills/review/review-style/SKILL.md +26 -6
- package/src/gdskills/bundled/skills/review/review-testing-practices/SKILL.md +35 -3
- package/src/gdskills/bundled/skills/review/review-verifier/SKILL.md +276 -0
- package/src/gdskills/contracts/review-finding.schema.json +119 -1
- package/src/gdskills/contracts/subagent-dispatch.schema.json +59 -3
- package/src/gdskills/bundled/skills/review/review-strict/SKILL.md +0 -328
package/docs/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Documentation
|
|
2
|
+
|
|
3
|
+
This directory separates documentation by purpose so current behavior, product
|
|
4
|
+
intent, implementation plans, and release evidence do not get mixed together.
|
|
5
|
+
|
|
6
|
+
## Current behavior
|
|
7
|
+
|
|
8
|
+
- [Developer documentation](docs/README.md) — entry point for setup,
|
|
9
|
+
architecture, modules, CLI behavior, and workspace lifecycle.
|
|
10
|
+
- [Complete setup and agent workflows](docs/complete-setup-and-agent-workflows.md)
|
|
11
|
+
— global installation, project configuration, command reference, scripts, and
|
|
12
|
+
copy-ready agent prompts.
|
|
13
|
+
- [Agent installation playbook](docs/agent-installation-playbook.md) — autonomous
|
|
14
|
+
Gherkin scenarios for complete setup, validation, repair, and handoff.
|
|
15
|
+
- [Documentation index](docs/index.md) — compact navigation for the generated
|
|
16
|
+
current-behavior reference.
|
|
17
|
+
|
|
18
|
+
## Product intent
|
|
19
|
+
|
|
20
|
+
- [Requirements roadmap](requirements/roadmap.md) — requirements packages and
|
|
21
|
+
their verified implementation state.
|
|
22
|
+
- [Managed Review Feedback Loop](requirements/managed-review-feedback-loop/README.md)
|
|
23
|
+
— requirements and contracts for managed review packages.
|
|
24
|
+
- [Keryx Memory Reliability](requirements/keryx-memory-reliability/README.md)
|
|
25
|
+
— corrective requirements and implementation tracking for side-effect-free
|
|
26
|
+
recall, accepted-only agent influence, durable lifecycle, and generated data.
|
|
27
|
+
|
|
28
|
+
## Plans and reports
|
|
29
|
+
|
|
30
|
+
- [Shared Agent Context Improvements Program](requirements/shared-agent-context-improvements-program/README.md)
|
|
31
|
+
— dependency-ordered implementation waves for twelve SAC improvement
|
|
32
|
+
packages, with copy-ready phase prompts, evidence gates, rollback, and a
|
|
33
|
+
live progress/statistics dashboard.
|
|
34
|
+
- [Keryx Improvements 1 — SAC, memory, and orchestration analysis](analysis/keryx-improvements-1/2026-08-14/report/en/report.md)
|
|
35
|
+
— integrated audit of Shared Agent Context and its Context Operations, Flow,
|
|
36
|
+
Harness/session, MCP, Security, knowledge-owner, worktree, and policy seams;
|
|
37
|
+
includes twelve independently deliverable future requirement packages.
|
|
38
|
+
- [Implementation plans](plans/) — bounded plans that may become cleanup
|
|
39
|
+
candidates after their acceptance criteria are implemented and verified.
|
|
40
|
+
- [Release readiness — 2026-07-10](report/release-readiness-2026-07-10/release-readiness.md)
|
|
41
|
+
— verification results, release blockers, and the prioritized cleanup plan.
|
|
42
|
+
- [Implementation spec](report/release-readiness-2026-07-10/implementation-spec.md)
|
|
43
|
+
— approved scope and acceptance criteria for this documentation pass.
|
|
44
|
+
|
|
45
|
+
## Documentation policy
|
|
46
|
+
|
|
47
|
+
- Repository documentation is English-only.
|
|
48
|
+
- `docs/docs/` describes current behavior and must be verified against source or
|
|
49
|
+
live CLI help.
|
|
50
|
+
- `docs/requirements/` describes intended behavior and must label implementation
|
|
51
|
+
status explicitly.
|
|
52
|
+
- Generated `.metaproject` artifacts are refreshed through the project CLI; raw
|
|
53
|
+
and reproducible outputs remain ignored according to the managed `.gitignore`
|
|
54
|
+
policy.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Keryx Shared Agent Context
|
|
2
|
+
Version: 1.5.0
|
|
3
|
+
|
|
4
|
+
## Назначение
|
|
5
|
+
|
|
6
|
+
Этот пакет описывает **текущий** local-first слой совместного контекста Keryx
|
|
7
|
+
(`src/sac/`, CLI `keryx workspace`, MCP `sac.*`, harness `workspace_*`).
|
|
8
|
+
Он даёт человеку и агенту воспроизводимый вход в работу от workspace, компонента
|
|
9
|
+
или flow: небольшой проверяемый обзор, адресное чтение деталей и безопасное
|
|
10
|
+
предложение нового знания по завершении работы.
|
|
11
|
+
|
|
12
|
+
## Статус
|
|
13
|
+
|
|
14
|
+
`implemented` (механизмы 0–5), плюс фаза 6, разбитая на две части: **6a** —
|
|
15
|
+
runtime-guard opt-in (`resolvePolicySelection`), реализована и проверена
|
|
16
|
+
(AC1–AC6, вся SAC-сюита 88/88 зелёная); **6b** — операторский процесс готовности
|
|
17
|
+
для реальных данных, частично (read-only `keryx workspace policy-readiness` и
|
|
18
|
+
playbook; runtime re-ingestion сырых receipts/outcomes остаётся). Фазы 0–5 и 6a
|
|
19
|
+
влиты в `main` и выпущены в `v0.2.32`; код lifecycle/CLI/MCP/harness с тех пор
|
|
20
|
+
расширен на `main` (в т.ч. `v0.2.35`). Точный статус и evidence приведены в
|
|
21
|
+
[Implementation plan](implementation-plan.md).
|
|
22
|
+
|
|
23
|
+
**Документационная правда (1.5.0):** более ранние versioned-заголовки этого
|
|
24
|
+
пакета помечали CLI/MCP/schema-enforcement как `future/planned` и прямо писали,
|
|
25
|
+
что runtime «не реализует SAC contracts». Это устарело относительно `src/sac/`
|
|
26
|
+
(~4.3k строк production + тесты: propose/review/accept, guarded owner-writers
|
|
27
|
+
wiki/memory/skill, receipt-integrity, access-receipt ledger; плюс
|
|
28
|
+
`src/commands/workspace.ts`, MCP `sac.*`, harness `workspace_*`). Норматив
|
|
29
|
+
этого пакета теперь —
|
|
30
|
+
как устроено **сейчас**. Спутниковые пакеты RP-01…RP-12 остаются future /
|
|
31
|
+
spec-ready и **не** отменяют этот runtime. Валидатор, который сверяется с
|
|
32
|
+
заголовками `future` в старых ревизиях или в RP-пакетах, получит ложный drift.
|
|
33
|
+
|
|
34
|
+
Важно: Phase 5 (policy experiment) сейчас подтверждает корректность механизма на
|
|
35
|
+
synthetic offline evidence и по умолчанию не включает production эффектов.
|
|
36
|
+
|
|
37
|
+
## Модель FWK
|
|
38
|
+
|
|
39
|
+
- **Facts** — evidence-linked, task-local и freshness-bound утверждения о
|
|
40
|
+
текущей работе. Fact не становится источником долгосрочного знания.
|
|
41
|
+
- **Work** — read-only проекция существующего Flow: выполненное, следующее,
|
|
42
|
+
блокировки и verification evidence. SAC никогда не создаёт второй tracker.
|
|
43
|
+
- **Know-how** — reviewed и reusable knowledge из memory, wiki и skills.
|
|
44
|
+
Необработанные транскрипты и скрытые рассуждения не являются Know-how.
|
|
45
|
+
|
|
46
|
+
## Документы
|
|
47
|
+
|
|
48
|
+
- [PRD](prd.md) — проблема, пользователи, требования, риски и результаты.
|
|
49
|
+
- [Specification](specification.md) — границы, функциональная surface,
|
|
50
|
+
интеграции и acceptance criteria.
|
|
51
|
+
- [Agent protocol](agent-protocol.md) — обязательное поведение агента при
|
|
52
|
+
read, wrap-up и proposal lifecycle.
|
|
53
|
+
- [Artifact lifecycle](artifact-lifecycle.md) — источники истины, freshness,
|
|
54
|
+
retention, supersession и deletion policy.
|
|
55
|
+
- [Metrics and validation](metrics-and-validation.md) — baseline, evals,
|
|
56
|
+
rollout/rollback и измеримые gates.
|
|
57
|
+
- [Implementation plan](implementation-plan.md) — delivery status фаз 0–6b
|
|
58
|
+
и исторические exit criteria.
|
|
59
|
+
- [Phase execution prompts](phase-execution-prompts.md) — утверждённые промты
|
|
60
|
+
для запуска и delivery-protocol каждой implementation phase.
|
|
61
|
+
- [Phase 4 usability report](phase-4-usability-report.md) — contract-only
|
|
62
|
+
walkthrough evidence.
|
|
63
|
+
- [Phase 5 policy experiment report](phase-5-policy-experiment-report.md) —
|
|
64
|
+
synthetic offline experiment evidence.
|
|
65
|
+
- [Phase 6 readiness](phase-6-real-opt-in-readiness.md) — 6a/6b split and
|
|
66
|
+
remaining real-data work.
|
|
67
|
+
- [Phase 6b operator playbook](phase-6b-operator-playbook.md) — operator
|
|
68
|
+
readiness process.
|
|
69
|
+
- [Design rationale](design-rationale.md) — решения и ограничения модели FWK.
|
|
70
|
+
- [Schemas](schemas/README.md) — JSON Schema, semantic-validation boundary и
|
|
71
|
+
полный positive/negative/replay fixture corpus.
|
|
72
|
+
|
|
73
|
+
## Scope
|
|
74
|
+
|
|
75
|
+
- Локальный workspace registry со ссылками на компоненты, repositories, flows,
|
|
76
|
+
evidence и approved knowledge; исходные артефакты не копируются.
|
|
77
|
+
- Bounded FWK overview и progressive retrieval через **существующие** CLI
|
|
78
|
+
(`keryx workspace overview|read`), MCP (`sac.overview`/`sac.read`, только
|
|
79
|
+
local stdio) и harness-tools (`workspace_overview`/`workspace_read`);
|
|
80
|
+
hash-chained access-receipt ledger.
|
|
81
|
+
- Evidence-linked session wrap-up, proposal queue, human review и guarded
|
|
82
|
+
promotion в существующие wiki/memory paths.
|
|
83
|
+
- Freshness, least disclosure, trusted ActorContext, local roles, redaction и
|
|
84
|
+
audit trail; proposal может быть принят только после проверяемого,
|
|
85
|
+
append-only review transition.
|
|
86
|
+
|
|
87
|
+
## Non-goals
|
|
88
|
+
|
|
89
|
+
- Новый task manager, дубликат Flow или новый primary store для wiki/memory.
|
|
90
|
+
- Хранение raw transcripts, secrets, PII, hidden reasoning или unrestricted
|
|
91
|
+
environment snapshots как knowledge.
|
|
92
|
+
- Обязательная облачная база, multi-tenant service, SSO или внешний catalog.
|
|
93
|
+
- UI/IDE/terminal shell как prerequisite первой поставки.
|
|
94
|
+
- Обучаемая или self-modifying access policy до воспроизводимых offline evals.
|
|
95
|
+
|
|
96
|
+
## Связанные модули
|
|
97
|
+
|
|
98
|
+
- [Keryx Context Operations](../keryx-context-operations/2026-07-12/README.md)
|
|
99
|
+
— владелец context assembly, retrieval trace и feedback lifecycle.
|
|
100
|
+
- [Keryx Project Agent Harness](../keryx-project-agent-harness/README.md) —
|
|
101
|
+
владелец сессий, approvals, worktrees и execution runtime.
|
|
102
|
+
- `src/flow`, `src/memory`, `src/wiki`, `src/gdgraph`, `src/mcp`,
|
|
103
|
+
`src/security`, `src/ctx`, `src/harness` — существующие интеграционные
|
|
104
|
+
границы; изменения в них требуют отдельных implementation flows.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.71",
|
|
4
4
|
"description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"test": "bun test",
|
|
42
42
|
"check": "tsc --noEmit && bun test",
|
|
43
43
|
"check:doc-links": "bun scripts/check-doc-links.ts",
|
|
44
|
+
"baseline:review-precision": "bun scripts/review-precision-baseline.ts",
|
|
44
45
|
"test:guards": "bun test src/lib/config-dir.ast.test.ts src/lib/config-dir.readers.test.ts src/lib/production-graph.test.ts src/harness/policy/profiles.test.ts src/lib/serve-server.test.ts"
|
|
45
46
|
},
|
|
46
47
|
"files": [
|
|
@@ -72,4 +73,4 @@
|
|
|
72
73
|
"protobufjs",
|
|
73
74
|
"sharp"
|
|
74
75
|
]
|
|
75
|
-
}
|
|
76
|
+
}
|
|
@@ -188,6 +188,13 @@ test("buildGraph resolves Python absolute, relative, and __init__.py imports", a
|
|
|
188
188
|
// !!! NEVER regenerate these goldens to make a diff disappear. If this test
|
|
189
189
|
// !!! fails, the TS/JS code path changed and AC4 is violated — fix the code,
|
|
190
190
|
// !!! not the golden.
|
|
191
|
+
//
|
|
192
|
+
// EXCEPTION, recorded rather than silently applied: flow 140 (P1 — dynamic
|
|
193
|
+
// imports counted as load-order cycles) intentionally adds an `importKind`
|
|
194
|
+
// field to every edge record, TS/JS included — the whole point of that flow
|
|
195
|
+
// is that the field was missing. That is a deliberate schema change, not a
|
|
196
|
+
// TS/JS behavior drift from Java/Python work, so the AC4 invariant above
|
|
197
|
+
// still holds; only the pinned literal needed the new field appended.
|
|
191
198
|
// ---------------------------------------------------------------------------
|
|
192
199
|
|
|
193
200
|
const GOLDEN_NODES_JSONL =
|
|
@@ -197,9 +204,9 @@ const GOLDEN_NODES_JSONL =
|
|
|
197
204
|
`{"id":"src/feature/value.ts","kind":"file","path":"src/feature/value.ts","language":"typescript"}\n`;
|
|
198
205
|
|
|
199
206
|
const GOLDEN_EDGES_JSONL =
|
|
200
|
-
`{"id":"edge:1","from":"src/feature/helper.js","to":"src/feature/value.ts","kind":"imports","specifier":"./value"}\n` +
|
|
201
|
-
`{"id":"edge:2","from":"src/feature/index.ts","to":"src/feature/style.css","kind":"asset","specifier":"./style.css"}\n` +
|
|
202
|
-
`{"id":"edge:3","from":"src/feature/index.ts","to":"src/feature/value.ts","kind":"imports","specifier":"./value"}\n`;
|
|
207
|
+
`{"id":"edge:1","from":"src/feature/helper.js","to":"src/feature/value.ts","kind":"imports","specifier":"./value","importKind":"import-statement"}\n` +
|
|
208
|
+
`{"id":"edge:2","from":"src/feature/index.ts","to":"src/feature/style.css","kind":"asset","specifier":"./style.css","importKind":"import-statement"}\n` +
|
|
209
|
+
`{"id":"edge:3","from":"src/feature/index.ts","to":"src/feature/value.ts","kind":"imports","specifier":"./value","importKind":"import-statement"}\n`;
|
|
203
210
|
|
|
204
211
|
test("buildGraph output is byte-identical for a TS/JS-only project (AC4 guard)", async () => {
|
|
205
212
|
const root = uniqueTestRoot(tmpdir(), "keryx-gdgraph-lang-regression");
|
package/src/gdgraph/build.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import type { GraphData, GraphEdge, GraphNode } from "./types";
|
|
4
|
+
import type { GraphData, GraphEdge, GraphNode, ImportKind, TranspilerImportKind } from "./types";
|
|
5
|
+
import { UNKNOWN_IMPORT_KIND } from "./types";
|
|
5
6
|
|
|
6
7
|
const SOURCE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".java", ".py"];
|
|
7
8
|
const SOURCE_RESOLUTION_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".d.ts", ".java", ".py"];
|
|
@@ -111,9 +112,9 @@ export async function buildGraph(projectRoot: string): Promise<BuildResult> {
|
|
|
111
112
|
// dropped), so the resolution metric is honest. TS/JS keeps the exact
|
|
112
113
|
// original guard (relative + tsconfig alias only) ⇒ byte-identical output.
|
|
113
114
|
const isLanguageAware = language === "java" || language === "python";
|
|
114
|
-
const
|
|
115
|
+
const records = extractImportRecords(content, language);
|
|
115
116
|
|
|
116
|
-
for (const specifier of
|
|
117
|
+
for (const { specifier, kind: importKind } of records) {
|
|
117
118
|
const resolved = resolveImport(projectRoot, file, specifier, fileSet, resolver);
|
|
118
119
|
const asset = resolved ? null : resolveAssetImport(projectRoot, file, specifier, resolver);
|
|
119
120
|
const shouldTrackUnresolved =
|
|
@@ -136,6 +137,7 @@ export async function buildGraph(projectRoot: string): Promise<BuildResult> {
|
|
|
136
137
|
to: resolved ?? asset ?? specifier,
|
|
137
138
|
kind: resolved ? "imports" : asset ? "asset" : "unresolved",
|
|
138
139
|
specifier,
|
|
140
|
+
importKind,
|
|
139
141
|
});
|
|
140
142
|
}
|
|
141
143
|
}
|
|
@@ -200,14 +202,27 @@ async function collectSourceFiles(projectRoot: string): Promise<SourceCollection
|
|
|
200
202
|
return { files: result.sort(), skippedDirectories: skippedDirectories.sort() };
|
|
201
203
|
}
|
|
202
204
|
|
|
203
|
-
|
|
205
|
+
type ImportRecord = { specifier: string; kind: ImportKind };
|
|
206
|
+
|
|
207
|
+
// P1 remediation (flow 140): union the transpiler and fallback extractors as
|
|
208
|
+
// before, but keep the transpiler's per-specifier kind alive instead of
|
|
209
|
+
// collapsing everything to a bare specifier string. `build()`'s edge-writing
|
|
210
|
+
// loop reads `record.kind` straight onto the edge, so cycle detection can
|
|
211
|
+
// later tell a load-order `import-statement` from a call-time `dynamic-import`
|
|
212
|
+
// instead of the previous single "imports" bucket.
|
|
213
|
+
function extractImportRecords(content: string, language: string): ImportRecord[] {
|
|
204
214
|
// Java/Python are not TS/JS syntax — the tsx transpiler cannot scan them
|
|
205
215
|
// (it throws today, which is why they already reach the fallback). Route them
|
|
206
216
|
// explicitly to the regex fallback that carries the java/python patterns,
|
|
207
217
|
// rather than depending on the transpiler always throwing. TS/JS keep the
|
|
208
218
|
// exact original transpiler-then-fallback path ⇒ byte-identical output.
|
|
219
|
+
// The fallback is a plain regex with no notion of import kind, so every
|
|
220
|
+
// Java/Python edge is marked UNKNOWN_IMPORT_KIND — never guessed (AC4).
|
|
209
221
|
if (language === "java" || language === "python") {
|
|
210
|
-
return extractImportSpecifiersFallback(content)
|
|
222
|
+
return extractImportSpecifiersFallback(content).map((specifier) => ({
|
|
223
|
+
specifier,
|
|
224
|
+
kind: UNKNOWN_IMPORT_KIND,
|
|
225
|
+
}));
|
|
211
226
|
}
|
|
212
227
|
// `Bun.Transpiler#scanImports` ERASES type-only imports: `import type {X} from
|
|
213
228
|
// "./m"` and `export type {X} from "./m"` are compiled away, so the transpiler
|
|
@@ -219,16 +234,46 @@ function extractImportSpecifiers(content: string, language: string): string[] {
|
|
|
219
234
|
// odd formatting), the fallback contributes the type-only ones.
|
|
220
235
|
const scanned = scanImportsOrEmpty(content);
|
|
221
236
|
const fallback = extractImportSpecifiersFallback(content);
|
|
222
|
-
|
|
237
|
+
|
|
238
|
+
const kindBySpecifier = new Map<string, ImportKind>();
|
|
239
|
+
for (const { specifier, kind } of scanned) {
|
|
240
|
+
const existing = kindBySpecifier.get(specifier);
|
|
241
|
+
// The same specifier can appear more than once in a file (a static import
|
|
242
|
+
// plus a separate `await import()` of the same path). A real static edge
|
|
243
|
+
// makes it a load-order dependency regardless of what else also imports
|
|
244
|
+
// it dynamically elsewhere in the file, so a non-dynamic kind always wins
|
|
245
|
+
// over a dynamic one already recorded for the same specifier.
|
|
246
|
+
if (!existing || (existing === "dynamic-import" && kind !== "dynamic-import")) {
|
|
247
|
+
kindBySpecifier.set(specifier, kind);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Fallback-only specifiers (not seen by the transpiler at all — e.g.
|
|
251
|
+
// type-only imports) get the explicit unknown/static marker, never a
|
|
252
|
+
// guessed kind (AC4). A specifier the transpiler DID see keeps its real
|
|
253
|
+
// kind; the fallback never overrides it.
|
|
254
|
+
for (const specifier of fallback) {
|
|
255
|
+
if (!kindBySpecifier.has(specifier)) {
|
|
256
|
+
kindBySpecifier.set(specifier, UNKNOWN_IMPORT_KIND);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return [...kindBySpecifier.entries()]
|
|
261
|
+
.map(([specifier, kind]) => ({ specifier, kind }))
|
|
262
|
+
.sort((a, b) => a.specifier.localeCompare(b.specifier));
|
|
223
263
|
}
|
|
224
264
|
|
|
225
|
-
|
|
265
|
+
type ScannedImport = { specifier: string; kind: TranspilerImportKind };
|
|
266
|
+
|
|
267
|
+
function scanImportsOrEmpty(content: string): ScannedImport[] {
|
|
226
268
|
try {
|
|
227
269
|
const scanner = new Bun.Transpiler({ loader: "tsx" });
|
|
228
270
|
return scanner
|
|
229
271
|
.scanImports(content)
|
|
230
|
-
.
|
|
231
|
-
|
|
272
|
+
.filter(
|
|
273
|
+
(entry): entry is { path: string; kind: TranspilerImportKind } =>
|
|
274
|
+
typeof entry.path === "string" && entry.path.length > 0,
|
|
275
|
+
)
|
|
276
|
+
.map((entry) => ({ specifier: entry.path, kind: entry.kind }));
|
|
232
277
|
} catch {
|
|
233
278
|
// Unparseable source ⇒ the regex fallback alone still yields the imports.
|
|
234
279
|
return [];
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// P1 remediation (flow 140) — `keryx gdgraph query cycles` was folding
|
|
2
|
+
// `await import()` edges into the load-order cycle count. `Bun.Transpiler
|
|
3
|
+
// #scanImports` already reports whether a specifier is a static
|
|
4
|
+
// import-statement, a dynamic-import, a require-call, etc.; `build.ts` threw
|
|
5
|
+
// that classification away one line after receiving it. These tests are
|
|
6
|
+
// written FIRST (TDD RED) against the frozen acceptance criteria in
|
|
7
|
+
// `.metaproject/flows/140-2026-08-07-gdgraph-dynamic-import-edges/acceptance-criteria.md`.
|
|
8
|
+
//
|
|
9
|
+
// Conventions mirror build.test.ts / build-lang.test.ts: uniqueTestRoot(),
|
|
10
|
+
// reset(root), buildGraph(root), loadGraph(root).
|
|
11
|
+
|
|
12
|
+
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import { expect, test } from "bun:test";
|
|
16
|
+
import { buildGraph } from "./build";
|
|
17
|
+
import { getCycles, loadGraph } from "./query";
|
|
18
|
+
import { uniqueTestRoot } from "../lib/test-tmp";
|
|
19
|
+
|
|
20
|
+
async function reset(root: string): Promise<void> {
|
|
21
|
+
await rm(root, { recursive: true, force: true });
|
|
22
|
+
await mkdir(root, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// AC1 — every edge the transpiler produced carries the kind scanImports
|
|
27
|
+
// actually returned, and it survives to the written edge record.
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
test("AC1 — the transpiler's import kind survives onto the written edge record", async () => {
|
|
31
|
+
const root = uniqueTestRoot(tmpdir(), "keryx-gdgraph-import-kind-ac1");
|
|
32
|
+
await reset(root);
|
|
33
|
+
await mkdir(path.join(root, "src"), { recursive: true });
|
|
34
|
+
await writeFile(
|
|
35
|
+
path.join(root, "src", "index.ts"),
|
|
36
|
+
[
|
|
37
|
+
"import { staticValue } from './static';",
|
|
38
|
+
"const loadDynamic = async () => { const { dynamicValue } = await import('./dynamic'); return dynamicValue; };",
|
|
39
|
+
"const required = require('./required');",
|
|
40
|
+
"export const result = { staticValue, loadDynamic, required };",
|
|
41
|
+
"",
|
|
42
|
+
].join("\n"),
|
|
43
|
+
);
|
|
44
|
+
await writeFile(path.join(root, "src", "static.ts"), "export const staticValue = 1;\n");
|
|
45
|
+
await writeFile(path.join(root, "src", "dynamic.ts"), "export const dynamicValue = 2;\n");
|
|
46
|
+
await writeFile(path.join(root, "src", "required.ts"), "export const requiredValue = 3;\n");
|
|
47
|
+
|
|
48
|
+
await buildGraph(root);
|
|
49
|
+
const graph = await loadGraph(root);
|
|
50
|
+
const edgesFromIndex = graph.edges.filter(
|
|
51
|
+
(edge) => edge.from === "src/index.ts" && edge.kind === "imports",
|
|
52
|
+
);
|
|
53
|
+
const importKindByTarget = new Map(edgesFromIndex.map((edge) => [edge.to, edge.importKind]));
|
|
54
|
+
|
|
55
|
+
expect(importKindByTarget.get("src/static.ts")).toBe("import-statement");
|
|
56
|
+
expect(importKindByTarget.get("src/dynamic.ts")).toBe("dynamic-import");
|
|
57
|
+
expect(importKindByTarget.get("src/required.ts")).toBe("require-call");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("a specifier imported both statically and dynamically in the same file keeps the static classification", async () => {
|
|
61
|
+
const root = uniqueTestRoot(tmpdir(), "keryx-gdgraph-import-kind-mixed-specifier");
|
|
62
|
+
await reset(root);
|
|
63
|
+
await mkdir(path.join(root, "src"), { recursive: true });
|
|
64
|
+
await writeFile(
|
|
65
|
+
path.join(root, "src", "index.ts"),
|
|
66
|
+
[
|
|
67
|
+
"import './shared';",
|
|
68
|
+
"const loadAgain = async () => { await import('./shared'); };",
|
|
69
|
+
"export const result = loadAgain;",
|
|
70
|
+
"",
|
|
71
|
+
].join("\n"),
|
|
72
|
+
);
|
|
73
|
+
await writeFile(path.join(root, "src", "shared.ts"), "export const shared = true;\n");
|
|
74
|
+
|
|
75
|
+
await buildGraph(root);
|
|
76
|
+
const graph = await loadGraph(root);
|
|
77
|
+
const edge = graph.edges.find((item) => item.from === "src/index.ts" && item.to === "src/shared.ts");
|
|
78
|
+
|
|
79
|
+
expect(edge?.importKind).toBe("import-statement");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// AC2 — a fixture reproducing the target's shape: one static edge, one
|
|
84
|
+
// dynamic-import edge back, no longer reported as a load-order cycle.
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
test("AC2 — a cycle closed only through a dynamic-import edge is no longer reported as load-order", async () => {
|
|
88
|
+
const root = uniqueTestRoot(tmpdir(), "keryx-gdgraph-cycle-mixed");
|
|
89
|
+
await reset(root);
|
|
90
|
+
await mkdir(path.join(root, "bot", "commands"), { recursive: true });
|
|
91
|
+
// Mirrors the target's shape: commands/menu.ts statically imports
|
|
92
|
+
// callbacks.ts; callbacks.ts reaches back into menu.ts only via `await
|
|
93
|
+
// import()`.
|
|
94
|
+
await writeFile(
|
|
95
|
+
path.join(root, "bot", "commands", "menu.ts"),
|
|
96
|
+
"import { registerCallback } from '../callbacks';\nexport const menu = () => registerCallback();\n",
|
|
97
|
+
);
|
|
98
|
+
await writeFile(
|
|
99
|
+
path.join(root, "bot", "callbacks.ts"),
|
|
100
|
+
[
|
|
101
|
+
"export const registerCallback = () => 1;",
|
|
102
|
+
"export const handleMenuCallback = async () => {",
|
|
103
|
+
" const { menu } = await import('./commands/menu');",
|
|
104
|
+
" return menu();",
|
|
105
|
+
"};",
|
|
106
|
+
"",
|
|
107
|
+
].join("\n"),
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
await buildGraph(root);
|
|
111
|
+
const graph = await loadGraph(root);
|
|
112
|
+
const cycles = getCycles(graph);
|
|
113
|
+
|
|
114
|
+
expect(
|
|
115
|
+
cycles.some((cycle) => cycle.includes("bot/callbacks.ts") && cycle.includes("bot/commands/menu.ts")),
|
|
116
|
+
).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// AC3 — classification both ways, same two-file cycle shape.
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
test("AC3 — a two-file cycle formed by static imports IS reported", async () => {
|
|
124
|
+
const root = uniqueTestRoot(tmpdir(), "keryx-gdgraph-cycle-static");
|
|
125
|
+
await reset(root);
|
|
126
|
+
await mkdir(path.join(root, "src"), { recursive: true });
|
|
127
|
+
await writeFile(path.join(root, "src", "a.ts"), "import { b } from './b';\nexport const a = () => b();\n");
|
|
128
|
+
await writeFile(path.join(root, "src", "b.ts"), "import { a } from './a';\nexport const b = () => a();\n");
|
|
129
|
+
|
|
130
|
+
await buildGraph(root);
|
|
131
|
+
const graph = await loadGraph(root);
|
|
132
|
+
const cycles = getCycles(graph);
|
|
133
|
+
|
|
134
|
+
expect(cycles.some((cycle) => cycle.includes("src/a.ts") && cycle.includes("src/b.ts"))).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("AC3 — the same two-file cycle formed by await import() is NOT reported as load-order", async () => {
|
|
138
|
+
const root = uniqueTestRoot(tmpdir(), "keryx-gdgraph-cycle-dynamic");
|
|
139
|
+
await reset(root);
|
|
140
|
+
await mkdir(path.join(root, "src"), { recursive: true });
|
|
141
|
+
await writeFile(
|
|
142
|
+
path.join(root, "src", "a.ts"),
|
|
143
|
+
"export const a = async () => { const { b } = await import('./b'); return b(); };\n",
|
|
144
|
+
);
|
|
145
|
+
await writeFile(
|
|
146
|
+
path.join(root, "src", "b.ts"),
|
|
147
|
+
"export const b = async () => { const { a } = await import('./a'); return a(); };\n",
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
await buildGraph(root);
|
|
151
|
+
const graph = await loadGraph(root);
|
|
152
|
+
const cycles = getCycles(graph);
|
|
153
|
+
|
|
154
|
+
expect(cycles.some((cycle) => cycle.includes("src/a.ts") && cycle.includes("src/b.ts"))).toBe(false);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
// AC4 — an edge found only by the regex fallback (never seen by scanImports)
|
|
159
|
+
// is marked with an explicit unknown/static marker, never inferred dynamic.
|
|
160
|
+
// A type-only import is the real-world case: `Bun.Transpiler#scanImports`
|
|
161
|
+
// erases `import type {...}` entirely, so it reaches the graph only through
|
|
162
|
+
// `extractImportSpecifiersFallback`.
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
test("AC4 — a fallback-only edge (type-only import) is marked unknown-static, never inferred dynamic", async () => {
|
|
166
|
+
const root = uniqueTestRoot(tmpdir(), "keryx-gdgraph-import-kind-ac4");
|
|
167
|
+
await reset(root);
|
|
168
|
+
await mkdir(path.join(root, "src"), { recursive: true });
|
|
169
|
+
await writeFile(
|
|
170
|
+
path.join(root, "src", "consumer.ts"),
|
|
171
|
+
"import type { Shape } from './types';\nexport const consumer: Shape = { ok: true } as Shape;\n",
|
|
172
|
+
);
|
|
173
|
+
await writeFile(path.join(root, "src", "types.ts"), "export interface Shape { ok: boolean }\n");
|
|
174
|
+
|
|
175
|
+
await buildGraph(root);
|
|
176
|
+
const graph = await loadGraph(root);
|
|
177
|
+
const edge = graph.edges.find((item) => item.from === "src/consumer.ts" && item.to === "src/types.ts");
|
|
178
|
+
|
|
179
|
+
expect(edge).toBeDefined();
|
|
180
|
+
expect(edge?.importKind).toBe("unknown-static");
|
|
181
|
+
expect(edge?.importKind).not.toBe("dynamic-import");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("AC4 — Java imports (fallback-only language) are marked unknown-static, never dynamic", async () => {
|
|
185
|
+
const root = uniqueTestRoot(tmpdir(), "keryx-gdgraph-import-kind-java");
|
|
186
|
+
await reset(root);
|
|
187
|
+
const javaRoot = path.join(root, "src", "main", "java", "com", "example");
|
|
188
|
+
await mkdir(javaRoot, { recursive: true });
|
|
189
|
+
await writeFile(
|
|
190
|
+
path.join(javaRoot, "Consumer.java"),
|
|
191
|
+
"package com.example;\nimport com.example.Model;\npublic class Consumer {}\n",
|
|
192
|
+
);
|
|
193
|
+
await writeFile(path.join(javaRoot, "Model.java"), "package com.example;\npublic class Model {}\n");
|
|
194
|
+
|
|
195
|
+
await buildGraph(root);
|
|
196
|
+
const graph = await loadGraph(root);
|
|
197
|
+
const edge = graph.edges.find(
|
|
198
|
+
(item) =>
|
|
199
|
+
item.from === "src/main/java/com/example/Consumer.java" &&
|
|
200
|
+
item.to === "src/main/java/com/example/Model.java",
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
expect(edge).toBeDefined();
|
|
204
|
+
expect(edge?.importKind).toBe("unknown-static");
|
|
205
|
+
});
|
package/src/gdgraph/query.ts
CHANGED
|
@@ -61,7 +61,12 @@ export function getCycles(graph: GraphData): string[][] {
|
|
|
61
61
|
adjacency.set(node.path, []);
|
|
62
62
|
}
|
|
63
63
|
for (const edge of graph.edges) {
|
|
64
|
-
|
|
64
|
+
// A `dynamic-import` (`await import()`) resolves at call time, not
|
|
65
|
+
// module-load time, so a cycle closed only through one is not the
|
|
66
|
+
// load-order cycle this query answers (P1, flow 140). Excluding it here
|
|
67
|
+
// — rather than reclassifying `edge.kind` — leaves orphans/affected
|
|
68
|
+
// untouched (AC5): both still treat the edge as a normal import.
|
|
69
|
+
if (edge.kind !== "imports" || edge.importKind === "dynamic-import") {
|
|
65
70
|
continue;
|
|
66
71
|
}
|
|
67
72
|
adjacency.get(edge.from)?.push(edge.to);
|
package/src/gdgraph/types.ts
CHANGED
|
@@ -5,12 +5,46 @@ export type GraphNode = {
|
|
|
5
5
|
language: "typescript" | "javascript" | "java" | "python" | "asset";
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
+
// Import classification straight from `Bun.Transpiler#scanImports` (P1
|
|
9
|
+
// remediation, flow 140). A static `import`/`export ... from` statement is a
|
|
10
|
+
// load-order dependency; `dynamic-import` (`await import()`) resolves at call
|
|
11
|
+
// time and is not. The full union mirrors every literal `scanImports` can
|
|
12
|
+
// return (bun-types `ImportKind`), so a transpiler-found edge always carries
|
|
13
|
+
// the value the transpiler actually reported — never a guess.
|
|
14
|
+
export type TranspilerImportKind =
|
|
15
|
+
| "import-statement"
|
|
16
|
+
| "require-call"
|
|
17
|
+
| "require-resolve"
|
|
18
|
+
| "dynamic-import"
|
|
19
|
+
| "import-rule"
|
|
20
|
+
| "url-token"
|
|
21
|
+
| "internal"
|
|
22
|
+
| "entry-point-run"
|
|
23
|
+
| "entry-point-build";
|
|
24
|
+
|
|
25
|
+
// A specifier found ONLY by the regex fallback (`extractImportSpecifiersFallback`
|
|
26
|
+
// in build.ts) carries no real kind from the transpiler — the fallback is a
|
|
27
|
+
// plain regex with no notion of "static" vs "dynamic". Never infer one;
|
|
28
|
+
// `UNKNOWN_IMPORT_KIND` marks it explicitly and cycle detection treats it as
|
|
29
|
+
// load-order (the pre-fix behavior), so fallback-only edges are never
|
|
30
|
+
// silently excluded from a real cycle.
|
|
31
|
+
export const UNKNOWN_IMPORT_KIND = "unknown-static" as const;
|
|
32
|
+
|
|
33
|
+
export type ImportKind = TranspilerImportKind | typeof UNKNOWN_IMPORT_KIND;
|
|
34
|
+
|
|
8
35
|
export type GraphEdge = {
|
|
9
36
|
id: string;
|
|
10
37
|
from: string;
|
|
11
38
|
to: string;
|
|
12
39
|
kind: "imports" | "asset" | "unresolved";
|
|
13
40
|
specifier: string;
|
|
41
|
+
// Provenance/kind of the specifier that produced this edge (P1, flow 140).
|
|
42
|
+
// `buildGraph()` always sets this. Optional (not required) so edge literals
|
|
43
|
+
// constructed before this field existed — test fixtures elsewhere in the
|
|
44
|
+
// repo, or graphs persisted by an older `keryx gdgraph build` — stay valid;
|
|
45
|
+
// `getCycles` treats a missing value as load-order, matching pre-fix
|
|
46
|
+
// behavior rather than crashing or silently mis-classifying.
|
|
47
|
+
importKind?: ImportKind;
|
|
14
48
|
};
|
|
15
49
|
|
|
16
50
|
export type GraphData = {
|