agentplugged 1.3.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -4
- package/dist/agent-commands.js +20432 -35457
- package/dist/bin.js +2 -3
- package/dist/boundary-rules.js +1 -0
- package/dist/boundary-rules2.js +52 -0
- package/dist/cli.js +7 -3
- package/dist/commit-documents.js +81 -0
- package/dist/dist.js +22729 -0
- package/dist/dist2.js +9 -0
- package/dist/esm.js +8 -16
- package/dist/forge-follow.js +6 -7
- package/dist/grammars/LICENSE-tree-sitter-wasm +21 -0
- package/dist/grammars/LICENSE-web-tree-sitter +21 -0
- package/dist/grammars/tree-sitter-go.wasm +0 -0
- package/dist/grammars/tree-sitter-java.wasm +0 -0
- package/dist/grammars/tree-sitter-javascript.wasm +0 -0
- package/dist/grammars/tree-sitter-php.wasm +0 -0
- package/dist/grammars/tree-sitter-python.wasm +0 -0
- package/dist/grammars/tree-sitter-rust.wasm +0 -0
- package/dist/grammars/tree-sitter-tsx.wasm +0 -0
- package/dist/grammars/tree-sitter-typescript.wasm +0 -0
- package/dist/grammars/web-tree-sitter.wasm +0 -0
- package/dist/hash.js +11594 -5733
- package/dist/helpers.js +271 -519
- package/dist/launcher-env.js +1 -2
- package/dist/launcher.js +4 -5
- package/dist/libsodium-wrappers.js +2 -4
- package/dist/libsodium-wrappers2.js +2 -4
- package/dist/main.js +10502 -2787
- package/dist/map-cache.js +8096 -0
- package/dist/map-update.js +16593 -0
- package/dist/map-update2.js +1 -0
- package/dist/method/agents/alex/SKILL.md +13 -29
- package/dist/method/agents/alex/agent.yaml +3 -3
- package/dist/method/agents/basile/SKILL.md +44 -0
- package/dist/method/agents/basile/agent.yaml +69 -0
- package/dist/method/agents/chloe/SKILL.md +12 -25
- package/dist/method/agents/chloe/agent.yaml +3 -3
- package/dist/method/agents/ethan/SKILL.md +8 -9
- package/dist/method/agents/ethan/agent.yaml +9 -8
- package/dist/method/agents/hugo/SKILL.md +8 -8
- package/dist/method/agents/hugo/agent.yaml +9 -8
- package/dist/method/agents/iris/SKILL.md +11 -17
- package/dist/method/agents/iris/agent.yaml +4 -7
- package/dist/method/agents/izzy/SKILL.md +68 -91
- package/dist/method/agents/izzy/agent.yaml +8 -15
- package/dist/method/agents/jade/SKILL.md +7 -9
- package/dist/method/agents/jade/agent.yaml +9 -8
- package/dist/method/agents/julien/SKILL.md +38 -58
- package/dist/method/agents/lina/SKILL.md +7 -9
- package/dist/method/agents/lina/agent.yaml +9 -8
- package/dist/method/agents/lou/SKILL.md +4 -11
- package/dist/method/agents/lou/agent.yaml +2 -2
- package/dist/method/agents/maya/SKILL.md +12 -19
- package/dist/method/agents/maya/agent.yaml +2 -4
- package/dist/method/agents/milo/SKILL.md +21 -32
- package/dist/method/agents/noah/SKILL.md +105 -63
- package/dist/method/agents/noah/agent.yaml +15 -35
- package/dist/method/agents/noah/evals/routes.yaml +238 -0
- package/dist/method/agents/noah/references/decisions.md +59 -121
- package/dist/method/agents/noah/references/environment.md +25 -70
- package/dist/method/agents/noah/references/facts.md +22 -11
- package/dist/method/agents/noah/references/journey.md +26 -78
- package/dist/method/agents/noah/references/team.md +67 -190
- package/dist/method/agents/omar/SKILL.md +14 -30
- package/dist/method/agents/omar/agent.yaml +12 -6
- package/dist/method/agents/quentin/SKILL.md +7 -15
- package/dist/method/agents/quentin/agent.yaml +4 -2
- package/dist/method/agents/sam/SKILL.md +6 -8
- package/dist/method/agents/sam/agent.yaml +9 -8
- package/dist/method/agents/stella/SKILL.md +6 -14
- package/dist/method/agents/stella/agent.yaml +1 -1
- package/dist/method/agents/tess/SKILL.md +7 -14
- package/dist/method/agents/tess/agent.yaml +2 -2
- package/dist/method/agents/zoe/SKILL.md +9 -21
- package/dist/method/doctor.yaml +553 -13
- package/dist/method/models.yaml +12 -0
- package/dist/method/skills/acceptance-qa/SKILL.md +1 -1
- package/dist/method/skills/acceptance-qa/references/run.md +24 -32
- package/dist/method/skills/acceptance-qa/references/scenarios.md +16 -19
- package/dist/method/skills/acceptance-qa/references/verdicts.md +10 -16
- package/dist/method/skills/api-e2e-tests/SKILL.md +46 -46
- package/dist/method/skills/audit-pillars/SKILL.md +13 -10
- package/dist/method/skills/audit-pillars/references/demo.md +35 -0
- package/dist/method/skills/audit-pillars/references/pillars.md +40 -0
- package/dist/method/skills/audit-pillars/references/tech-debt.md +38 -0
- package/dist/method/skills/chain-stories/SKILL.md +5 -4
- package/dist/method/skills/chain-stories/references/merge-order.md +23 -0
- package/dist/method/skills/check-readiness/SKILL.md +1 -1
- package/dist/method/skills/check-requirements/SKILL.md +37 -72
- package/dist/method/skills/check-requirements/references/engine-proofs.md +16 -23
- package/dist/method/skills/check-requirements/references/proofs-and-gaps.md +22 -37
- package/dist/method/skills/critique-deliverable/SKILL.md +3 -3
- package/dist/method/skills/critique-deliverable/references/choose.md +12 -21
- package/dist/method/skills/critique-deliverable/references/format.md +14 -20
- package/dist/method/skills/critique-deliverable/references/gestures.md +14 -21
- package/dist/method/skills/critique-deliverable/references/judgment.md +4 -9
- package/dist/method/skills/critique-deliverable/references/methods.md +21 -0
- package/dist/method/skills/critique-deliverable/references/reconcile.md +6 -11
- package/dist/method/skills/deep-research/SKILL.md +16 -24
- package/dist/method/skills/deep-research/references/candidates.md +13 -25
- package/dist/method/skills/deep-research/references/external.md +15 -34
- package/dist/method/skills/deep-research/references/page-format.md +16 -42
- package/dist/method/skills/deep-research/references/types.md +16 -27
- package/dist/method/skills/develop-story/SKILL.md +56 -189
- package/dist/method/skills/develop-story/references/acceptance-tests.md +18 -71
- package/dist/method/skills/develop-story/references/quick-lane.md +20 -0
- package/dist/method/skills/diagnose-bug/SKILL.md +42 -53
- package/dist/method/skills/diagnose-bug/references/hypotheses.md +1 -2
- package/dist/method/skills/generate-knowledge-base/SKILL.md +57 -226
- package/dist/method/skills/generate-knowledge-base/assets/index-template.md +6 -15
- package/dist/method/skills/generate-knowledge-base/assets/page-template.md +11 -24
- package/dist/method/skills/generate-knowledge-base/references/living-docs.md +25 -64
- package/dist/method/skills/generate-knowledge-base/references/reuse-map.md +9 -18
- package/dist/method/skills/ideation/SKILL.md +16 -23
- package/dist/method/skills/ideation/references/close.md +18 -14
- package/dist/method/skills/ideation/references/converge.md +11 -21
- package/dist/method/skills/ideation/references/stances.md +13 -17
- package/dist/method/skills/ideation/references/techniques.md +20 -0
- package/dist/method/skills/improve-code/SKILL.md +42 -54
- package/dist/method/skills/pitch/SKILL.md +16 -19
- package/dist/method/skills/pitch/references/deck.md +15 -18
- package/dist/method/skills/pitch/references/design-implementation.md +13 -21
- package/dist/method/skills/pitch/references/design.md +21 -0
- package/dist/method/skills/pitch/references/story.md +13 -18
- package/dist/method/skills/plan-sprint/SKILL.md +50 -151
- package/dist/method/skills/plan-sprint/references/rituals.md +40 -0
- package/dist/method/skills/prepare-go-live/SKILL.md +59 -0
- package/dist/method/skills/resolve-merge-conflict/SKILL.md +40 -65
- package/dist/method/skills/review-after-launch/SKILL.md +56 -0
- package/dist/method/skills/review-story/SKILL.md +44 -72
- package/dist/method/skills/review-story/references/amend-story.md +8 -13
- package/dist/method/skills/review-story/references/export.md +7 -17
- package/dist/method/skills/review-story/references/free-review.md +9 -20
- package/dist/method/skills/review-story/references/judge.md +25 -37
- package/dist/method/skills/review-story/references/tests.md +29 -0
- package/dist/method/skills/run-acceptance/SKILL.md +58 -0
- package/dist/method/skills/run-acceptance/references/template.md +39 -0
- package/dist/method/skills/run-retrospective/SKILL.md +50 -189
- package/dist/method/skills/security-review-architecture/SKILL.md +46 -109
- package/dist/method/skills/security-review-architecture/references/spec-review.md +27 -38
- package/dist/method/skills/setup-ci/SKILL.md +46 -109
- package/dist/method/skills/setup-ci/assets/access-check.mjs +116 -0
- package/dist/method/skills/setup-ci/assets/security-github.yml +41 -0
- package/dist/method/skills/setup-ci/assets/security-gitlab.yml +22 -0
- package/dist/method/skills/setup-ci/assets/stack-dotnet-github.yml +118 -0
- package/dist/method/skills/setup-ci/assets/stack-dotnet-gitlab.yml +63 -0
- package/dist/method/skills/setup-ci/assets/stack-go-github.yml +118 -0
- package/dist/method/skills/setup-ci/assets/stack-go-gitlab.yml +63 -0
- package/dist/method/skills/setup-ci/assets/stack-jvm-gradle-github.yml +134 -0
- package/dist/method/skills/setup-ci/assets/stack-jvm-gradle-gitlab.yml +71 -0
- package/dist/method/skills/setup-ci/assets/stack-jvm-maven-github.yml +133 -0
- package/dist/method/skills/setup-ci/assets/stack-jvm-maven-gitlab.yml +70 -0
- package/dist/method/skills/setup-ci/assets/stack-python-github.yml +123 -0
- package/dist/method/skills/setup-ci/assets/stack-python-gitlab.yml +68 -0
- package/dist/method/skills/setup-ci/assets/stack-rust-github.yml +107 -0
- package/dist/method/skills/setup-ci/assets/stack-rust-gitlab.yml +64 -0
- package/dist/method/skills/setup-ci/references/security.md +39 -0
- package/dist/method/skills/setup-ci/references/stacks.md +22 -0
- package/dist/method/skills/split-into-stories/SKILL.md +59 -192
- package/dist/method/skills/split-into-stories/assets/story.md.tmpl +15 -4
- package/dist/method/skills/strategy/SKILL.md +20 -25
- package/dist/method/skills/strategy/references/costs.md +12 -26
- package/dist/method/skills/strategy/references/gestures.md +12 -21
- package/dist/method/skills/strategy/references/innovate.md +19 -37
- package/dist/method/skills/strategy/references/solve.md +19 -32
- package/dist/method/skills/test-idea/SKILL.md +5 -5
- package/dist/method/skills/test-idea/assets/prfaq-template.md +1 -1
- package/dist/method/skills/test-idea/references/faq.md +12 -29
- package/dist/method/skills/test-idea/references/forge.md +19 -30
- package/dist/method/skills/test-idea/references/personas.md +16 -19
- package/dist/method/skills/test-idea/references/prfaq.md +31 -31
- package/dist/method/skills/workshop/SKILL.md +10 -16
- package/dist/method/skills/workshop/references/create.md +28 -45
- package/dist/method/skills/workshop/references/customize.md +17 -42
- package/dist/method/skills/workshop/references/evaluate.md +11 -42
- package/dist/method/skills/workshop/references/rules.md +17 -26
- package/dist/method/skills/write-architecture/SKILL.md +62 -220
- package/dist/method/skills/write-architecture/references/guard.md +12 -27
- package/dist/method/skills/write-architecture/references/self-check.md +22 -38
- package/dist/method/skills/write-brand/SKILL.md +2 -2
- package/dist/method/skills/write-brand/references/identity.md +21 -22
- package/dist/method/skills/write-brand/references/platform.md +13 -20
- package/dist/method/skills/write-brand/references/rules.md +16 -22
- package/dist/method/skills/write-brief/SKILL.md +55 -149
- package/dist/method/skills/write-brief/assets/brief-template.md +7 -5
- package/dist/method/skills/write-brief/references/brief-checklist.md +18 -30
- package/dist/method/skills/write-content/SKILL.md +53 -210
- package/dist/method/skills/write-content/references/content-checklist.md +32 -50
- package/dist/method/skills/write-positioning/SKILL.md +53 -197
- package/dist/method/skills/write-positioning/references/positioning-checklist.md +15 -41
- package/dist/method/skills/write-positioning/references/seo-checklist.md +26 -51
- package/dist/method/skills/write-prd/SKILL.md +52 -179
- package/dist/method/skills/write-prd/references/prd-checklist.md +25 -54
- package/dist/method/skills/write-sales/SKILL.md +48 -212
- package/dist/method/skills/write-sales/references/before-you-send.md +21 -49
- package/dist/method/skills/write-sales/references/sales-checklist.md +19 -69
- package/dist/method/skills/write-spec/references/spec-guide.md +20 -23
- package/dist/method/skills/write-ux-spec/SKILL.md +57 -168
- package/dist/method/skills/write-ux-spec/references/accessibility-check.md +13 -72
- package/dist/method/skills/write-ux-spec/references/mockups.md +6 -68
- package/dist/method/skills/write-ux-spec/references/tokens-and-sketches.md +8 -81
- package/dist/method/skills/write-ux-spec/references/ux-checklist.md +12 -67
- package/dist/product-root.js +96 -20743
- package/dist/project-command.js +776 -0
- package/dist/project-command2.js +1 -0
- package/dist/read-command.js +19 -0
- package/dist/rolldown-runtime.js +1 -2
- package/dist/sheet-command.js +58 -0
- package/dist/webauthn-verify.js +3 -5
- package/package.json +13 -11
- package/dist/method/agents/noah/references/conduct.md +0 -102
- package/dist/method/skills/api-e2e-tests/references/frameworks.md +0 -58
- package/dist/method/skills/api-e2e-tests/references/generate.md +0 -67
- package/dist/method/skills/audit-pillars/references/accessibility.md +0 -13
- package/dist/method/skills/audit-pillars/references/architecture.md +0 -13
- package/dist/method/skills/audit-pillars/references/dependencies.md +0 -13
- package/dist/method/skills/audit-pillars/references/format.md +0 -20
- package/dist/method/skills/audit-pillars/references/interface.md +0 -13
- package/dist/method/skills/audit-pillars/references/method.md +0 -33
- package/dist/method/skills/audit-pillars/references/performance.md +0 -17
- package/dist/method/skills/audit-pillars/references/quality.md +0 -11
- package/dist/method/skills/audit-pillars/references/security.md +0 -13
- package/dist/method/skills/audit-pillars/references/tests.md +0 -17
- package/dist/method/skills/critique-deliverable/references/assumption-audit.md +0 -11
- package/dist/method/skills/critique-deliverable/references/compare-existing.md +0 -13
- package/dist/method/skills/critique-deliverable/references/devils-advocate.md +0 -13
- package/dist/method/skills/critique-deliverable/references/divergence.md +0 -15
- package/dist/method/skills/critique-deliverable/references/domain-expert.md +0 -14
- package/dist/method/skills/critique-deliverable/references/extreme-user.md +0 -13
- package/dist/method/skills/critique-deliverable/references/failure-modes.md +0 -13
- package/dist/method/skills/critique-deliverable/references/first-principles.md +0 -12
- package/dist/method/skills/critique-deliverable/references/inversion.md +0 -13
- package/dist/method/skills/critique-deliverable/references/pre-mortem.md +0 -12
- package/dist/method/skills/critique-deliverable/references/red-team.md +0 -12
- package/dist/method/skills/critique-deliverable/references/reframe.md +0 -12
- package/dist/method/skills/critique-deliverable/references/second-order.md +0 -12
- package/dist/method/skills/critique-deliverable/references/socratic.md +0 -15
- package/dist/method/skills/critique-deliverable/references/steelman.md +0 -13
- package/dist/method/skills/critique-deliverable/references/subtraction.md +0 -12
- package/dist/method/skills/develop-story/references/self-check.md +0 -46
- package/dist/method/skills/generate-knowledge-base/references/kb-checklist.md +0 -49
- package/dist/method/skills/generate-knowledge-base/references/repository-scan.md +0 -48
- package/dist/method/skills/ideation/references/choose.md +0 -21
- package/dist/method/skills/ideation/references/gestures.md +0 -15
- package/dist/method/skills/ideation/references/techniques-borrowed.md +0 -8
- package/dist/method/skills/ideation/references/techniques-deep.md +0 -12
- package/dist/method/skills/ideation/references/techniques-frames.md +0 -12
- package/dist/method/skills/ideation/references/techniques-futures.md +0 -8
- package/dist/method/skills/ideation/references/techniques-limits.md +0 -9
- package/dist/method/skills/ideation/references/techniques-people.md +0 -7
- package/dist/method/skills/ideation/references/techniques-sparks.md +0 -10
- package/dist/method/skills/ideation/references/techniques-together.md +0 -11
- package/dist/method/skills/order-merges/SKILL.md +0 -71
- package/dist/method/skills/pitch/references/deck-types.md +0 -17
- package/dist/method/skills/pitch/references/design-methods.md +0 -45
- package/dist/method/skills/pitch/references/design-thinking.md +0 -28
- package/dist/method/skills/pitch/references/story-frameworks.md +0 -39
- package/dist/method/skills/quick-lane/SKILL.md +0 -143
- package/dist/method/skills/review-tests/SKILL.md +0 -32
- package/dist/method/skills/review-tests/references/check.md +0 -25
- package/dist/method/skills/review-tests/references/grid.md +0 -27
- package/dist/method/skills/round-table/SKILL.md +0 -31
- package/dist/method/skills/round-table/evals/cases.yaml +0 -27
- package/dist/method/skills/round-table/references/cast.md +0 -14
- package/dist/method/skills/round-table/references/gestures.md +0 -24
- package/dist/method/skills/round-table/references/guests.md +0 -17
- package/dist/method/skills/round-table/references/independence.md +0 -21
- package/dist/method/skills/round-table/references/rooms.md +0 -29
- package/dist/method/skills/round-table/references/three-views.md +0 -17
- package/dist/method/skills/round-table/references/turns.md +0 -19
- package/dist/method/skills/strategy/references/innovate-frameworks.md +0 -48
- package/dist/method/skills/strategy/references/solve-methods.md +0 -45
- package/dist/method/skills/test-idea/references/bets.md +0 -14
- package/dist/method/skills/test-idea/references/concept.md +0 -24
- package/dist/method/skills/test-idea/references/verdict.md +0 -26
- package/dist/method/skills/workshop/references/analyze.md +0 -36
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
import { Vs as ErrorCode } from "./hash.js";
|
|
2
|
+
import { B as AgentPluggedError, C as nameWords, F as PROFILE_PATH, I as acceptanceText, L as parseKept, P as ACCEPTANCE_PLACES, R as renderProfile, _ as findBricks, d as bricksOfMap, r as loadCodeMap, v as matchingSources, x as stem, y as searchWords, z as withSource } from "./map-cache.js";
|
|
3
|
+
// ../../packages/core/dist/project-profile/repo-map.js
|
|
4
|
+
/**
|
|
5
|
+
* The map of a repository for one piece of work: the files that matter for a request, each with the
|
|
6
|
+
* symbols it exports (functions, classes, types) and their signatures, within a budget of tokens.
|
|
7
|
+
* It replaces the wide reads an agent makes to find its way. Pure and deterministic: the same
|
|
8
|
+
* bricks and the same request always give the same text, and the text never exceeds the budget.
|
|
9
|
+
*
|
|
10
|
+
* Files are ranked the way a reader would: the files the request names come first, then those whose
|
|
11
|
+
* path or symbols carry its words (a word that is everywhere weighs little), and the files the rest
|
|
12
|
+
* of the code leans on break ties and fill what is left.
|
|
13
|
+
*/
|
|
14
|
+
/** Tokens the map takes by default, and the most a caller may ask. */
|
|
15
|
+
const MAP_TOKENS = 1500;
|
|
16
|
+
const MAP_MAX_TOKENS = 3e3;
|
|
17
|
+
/** Characters per token assumed: low on purpose, code is dense, so the real count stays under. */
|
|
18
|
+
const CHARS_PER_TOKEN = 3;
|
|
19
|
+
/** Most symbols of one file, and of a file chosen only for its place in the code. */
|
|
20
|
+
const SYMBOLS_PER_FILE = 10;
|
|
21
|
+
const SYMBOLS_PER_FILLER = 3;
|
|
22
|
+
/** The share of the budget the files unrelated to the request may take. */
|
|
23
|
+
const FILLER_SHARE = .25;
|
|
24
|
+
/** The words of a request read at most. */
|
|
25
|
+
const REQUEST_CHARS = 8e3;
|
|
26
|
+
const REQUEST_WORDS = 300;
|
|
27
|
+
/** The tokens a text takes, estimated (never fewer than the real count for code). */
|
|
28
|
+
function mapTokens(text) {
|
|
29
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
30
|
+
}
|
|
31
|
+
const HEADER = "Repo map (files by relevance; line: exported declaration)";
|
|
32
|
+
/** The words of a request: its identifiers split, folded to roots, each once. */
|
|
33
|
+
function requestStems(request) {
|
|
34
|
+
const words = request.slice(0, REQUEST_CHARS).match(/[A-Za-z][A-Za-z0-9_$]{2,}/g) ?? [];
|
|
35
|
+
return [...new Set(searchWords(words.slice(0, REQUEST_WORDS * 4)))].slice(0, REQUEST_WORDS);
|
|
36
|
+
}
|
|
37
|
+
function candidatesOf(bricks, request) {
|
|
38
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
39
|
+
for (const brick of bricks) {
|
|
40
|
+
if (!brick.exported) continue;
|
|
41
|
+
const list = byFile.get(brick.path);
|
|
42
|
+
if (list === void 0) byFile.set(brick.path, [brick]);
|
|
43
|
+
else list.push(brick);
|
|
44
|
+
}
|
|
45
|
+
const wanted = requestStems(request);
|
|
46
|
+
const stems = /* @__PURE__ */ new Map();
|
|
47
|
+
const spread = /* @__PURE__ */ new Map();
|
|
48
|
+
for (const [path, list] of byFile) {
|
|
49
|
+
const inPath = new Set(nameWords(path).map(stem));
|
|
50
|
+
const names = /* @__PURE__ */ new Map();
|
|
51
|
+
const all = new Set(inPath);
|
|
52
|
+
for (const brick of list) {
|
|
53
|
+
const own = new Set(nameWords(brick.name).map(stem));
|
|
54
|
+
names.set(brick, own);
|
|
55
|
+
for (const word of own) all.add(word);
|
|
56
|
+
}
|
|
57
|
+
stems.set(path, {
|
|
58
|
+
path: inPath,
|
|
59
|
+
names
|
|
60
|
+
});
|
|
61
|
+
for (const word of all) spread.set(word, (spread.get(word) ?? 0) + 1);
|
|
62
|
+
}
|
|
63
|
+
const total = Math.max(1, byFile.size);
|
|
64
|
+
const weight = (word) => Math.log(1 + total / (spread.get(word) ?? total));
|
|
65
|
+
const out = [];
|
|
66
|
+
const sameName = /* @__PURE__ */ new Map();
|
|
67
|
+
for (const path of byFile.keys()) {
|
|
68
|
+
const name = path.split("/").at(-1) ?? path;
|
|
69
|
+
sameName.set(name, (sameName.get(name) ?? 0) + 1);
|
|
70
|
+
}
|
|
71
|
+
for (const [path, list] of byFile) {
|
|
72
|
+
const known = stems.get(path);
|
|
73
|
+
let relevance = 0;
|
|
74
|
+
const matched = /* @__PURE__ */ new Map();
|
|
75
|
+
for (const word of wanted) {
|
|
76
|
+
if (known.path.has(word)) relevance += 2 * weight(word);
|
|
77
|
+
let named = false;
|
|
78
|
+
for (const [brick, own] of known.names) {
|
|
79
|
+
if (!own.has(word)) continue;
|
|
80
|
+
named = true;
|
|
81
|
+
matched.set(brick, (matched.get(brick) ?? 0) + weight(word));
|
|
82
|
+
}
|
|
83
|
+
if (named) relevance += 3 * weight(word);
|
|
84
|
+
}
|
|
85
|
+
const base = path.split("/").at(-1) ?? path;
|
|
86
|
+
const mentioned = request.includes(path) ? 12 : base.length >= 6 && sameName.get(base) === 1 && request.includes(base) ? 6 : 0;
|
|
87
|
+
const leaned = list.reduce((sum, brick) => sum + brick.refs, 0);
|
|
88
|
+
out.push({
|
|
89
|
+
path,
|
|
90
|
+
bricks: list,
|
|
91
|
+
relevance,
|
|
92
|
+
mentioned,
|
|
93
|
+
centrality: Math.log(1 + leaned),
|
|
94
|
+
matched
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
const scoreOf = (c) => c.relevance + c.mentioned + .3 * c.centrality;
|
|
100
|
+
const byRank = (a, b) => scoreOf(b) - scoreOf(a) || (a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
101
|
+
/** The symbols of a file worth showing, best first: those the request names, then those most used. */
|
|
102
|
+
function symbolsOf(candidate, cap) {
|
|
103
|
+
return [...candidate.bricks].sort((a, b) => (candidate.matched.get(b) ?? 0) - (candidate.matched.get(a) ?? 0) || b.refs - a.refs || a.line - b.line).slice(0, cap);
|
|
104
|
+
}
|
|
105
|
+
const lineOf = (brick) => ` ${brick.line}: ${brick.decl}`;
|
|
106
|
+
/**
|
|
107
|
+
* The map of `bricks` for `request` (a story's title and sheet, a sentence), within `budget` tokens.
|
|
108
|
+
* Undefined when there is nothing exported to show.
|
|
109
|
+
*/
|
|
110
|
+
function repoMapOf(bricks, request, budget = MAP_TOKENS) {
|
|
111
|
+
const tokensLeft = Math.min(MAP_MAX_TOKENS, Math.max(0, Number.isFinite(budget) ? Math.floor(budget) : MAP_TOKENS));
|
|
112
|
+
const candidates = candidatesOf(bricks, request).sort(byRank);
|
|
113
|
+
if (candidates.length === 0 || tokensLeft === 0) return void 0;
|
|
114
|
+
const related = candidates.filter((c) => c.relevance > 0 || c.mentioned > 0);
|
|
115
|
+
const filler = candidates.filter((c) => c.relevance === 0 && c.mentioned === 0);
|
|
116
|
+
const room = tokensLeft * CHARS_PER_TOKEN - 57 - 64;
|
|
117
|
+
const fillerRoom = related.length === 0 ? room : Math.floor(room * FILLER_SHARE);
|
|
118
|
+
const blocks = [];
|
|
119
|
+
let chars = 0;
|
|
120
|
+
let shown = 0;
|
|
121
|
+
let fillerChars = 0;
|
|
122
|
+
let symbols = 0;
|
|
123
|
+
let left = candidates.length;
|
|
124
|
+
const take = (list, cap, limit, isFiller) => {
|
|
125
|
+
for (const candidate of list) {
|
|
126
|
+
const lines = symbolsOf(candidate, cap);
|
|
127
|
+
const fitting = [];
|
|
128
|
+
let size = candidate.path.length + 1;
|
|
129
|
+
for (const brick of lines) {
|
|
130
|
+
const add = lineOf(brick).length + 1;
|
|
131
|
+
if ((isFiller ? fillerChars : chars) + size + add > limit || chars + size + add > room) break;
|
|
132
|
+
fitting.push(brick);
|
|
133
|
+
size += add;
|
|
134
|
+
}
|
|
135
|
+
if (fitting.length === 0) continue;
|
|
136
|
+
fitting.sort((a, b) => a.line - b.line);
|
|
137
|
+
blocks.push(`${candidate.path}\n${fitting.map(lineOf).join("\n")}`);
|
|
138
|
+
chars += size;
|
|
139
|
+
if (isFiller) fillerChars += size;
|
|
140
|
+
shown += 1;
|
|
141
|
+
symbols += fitting.length;
|
|
142
|
+
left -= 1;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
take(related, SYMBOLS_PER_FILE, room, false);
|
|
146
|
+
take(filler, SYMBOLS_PER_FILLER, fillerRoom, true);
|
|
147
|
+
if (shown === 0) return void 0;
|
|
148
|
+
const footer = left > 0 ? `\n+${left} more files` : "";
|
|
149
|
+
const text = `${HEADER}\n${blocks.join("\n")}${footer}`;
|
|
150
|
+
return {
|
|
151
|
+
text,
|
|
152
|
+
tokens: mapTokens(text),
|
|
153
|
+
files: shown,
|
|
154
|
+
symbols,
|
|
155
|
+
omitted_files: left
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
// ../../packages/core/dist/project-profile/scan.js
|
|
159
|
+
/** Longest manifest read (bytes): a larger one is data, not a manifest. */
|
|
160
|
+
const MAX_MANIFEST = 2e5;
|
|
161
|
+
const MAX_LAYOUT = 12;
|
|
162
|
+
/** Root entries that are not product folders. */
|
|
163
|
+
const NOT_LAYOUT = /* @__PURE__ */ new Set([
|
|
164
|
+
"node_modules",
|
|
165
|
+
"dist",
|
|
166
|
+
"build",
|
|
167
|
+
"out",
|
|
168
|
+
"target",
|
|
169
|
+
"vendor",
|
|
170
|
+
"coverage",
|
|
171
|
+
"tmp",
|
|
172
|
+
"temp",
|
|
173
|
+
"bin",
|
|
174
|
+
"obj",
|
|
175
|
+
"venv",
|
|
176
|
+
"env",
|
|
177
|
+
"docs",
|
|
178
|
+
"doc",
|
|
179
|
+
"scripts",
|
|
180
|
+
"public",
|
|
181
|
+
"assets",
|
|
182
|
+
"images",
|
|
183
|
+
"licenses",
|
|
184
|
+
"license"
|
|
185
|
+
]);
|
|
186
|
+
const TEST_FOLDERS = /* @__PURE__ */ new Set([
|
|
187
|
+
"test",
|
|
188
|
+
"tests",
|
|
189
|
+
"__tests__",
|
|
190
|
+
"spec",
|
|
191
|
+
"specs",
|
|
192
|
+
"e2e"
|
|
193
|
+
]);
|
|
194
|
+
function lower(entries) {
|
|
195
|
+
return new Map(entries.map((entry) => [entry.toLowerCase(), entry]));
|
|
196
|
+
}
|
|
197
|
+
/** The script a manifest names `name`, as a command, or undefined. */
|
|
198
|
+
const pick = (record, name) => {
|
|
199
|
+
const value = record?.[name];
|
|
200
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
201
|
+
};
|
|
202
|
+
const asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
203
|
+
/** Libraries worth naming in the stack: a short, well-known set, found in a dependency list. */
|
|
204
|
+
const KNOWN_PACKAGES = [
|
|
205
|
+
["react", "React"],
|
|
206
|
+
["vue", "Vue"],
|
|
207
|
+
["svelte", "Svelte"],
|
|
208
|
+
["next", "Next.js"],
|
|
209
|
+
["nuxt", "Nuxt"],
|
|
210
|
+
["@angular/core", "Angular"],
|
|
211
|
+
["express", "Express"],
|
|
212
|
+
["fastify", "Fastify"],
|
|
213
|
+
["@nestjs/core", "NestJS"],
|
|
214
|
+
["vitest", "Vitest"],
|
|
215
|
+
["jest", "Jest"],
|
|
216
|
+
["mocha", "Mocha"],
|
|
217
|
+
["@playwright/test", "Playwright"],
|
|
218
|
+
["cypress", "Cypress"],
|
|
219
|
+
["@biomejs/biome", "Biome"],
|
|
220
|
+
["eslint", "ESLint"],
|
|
221
|
+
["prettier", "Prettier"]
|
|
222
|
+
];
|
|
223
|
+
async function scanNode(reader, out) {
|
|
224
|
+
const raw = await reader.text("package.json");
|
|
225
|
+
if (raw === void 0) return;
|
|
226
|
+
let manifest;
|
|
227
|
+
try {
|
|
228
|
+
manifest = asRecord(JSON.parse(raw));
|
|
229
|
+
} catch {
|
|
230
|
+
out.stack.push("Node.js project (package.json is not valid JSON)");
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (manifest === void 0) return;
|
|
234
|
+
const lockfiles = [
|
|
235
|
+
"pnpm-lock.yaml",
|
|
236
|
+
"yarn.lock",
|
|
237
|
+
"bun.lock",
|
|
238
|
+
"bun.lockb",
|
|
239
|
+
"package-lock.json"
|
|
240
|
+
].filter((name) => reader.entries.has(name));
|
|
241
|
+
const declared = pick(manifest, "packageManager")?.split("@")[0];
|
|
242
|
+
const manager = declared !== void 0 && declared !== "" ? declared : {
|
|
243
|
+
"pnpm-lock.yaml": "pnpm",
|
|
244
|
+
"yarn.lock": "yarn",
|
|
245
|
+
"bun.lock": "bun",
|
|
246
|
+
"bun.lockb": "bun",
|
|
247
|
+
"package-lock.json": "npm"
|
|
248
|
+
}[lockfiles[0] ?? ""] ?? "npm";
|
|
249
|
+
const typed = reader.entries.has("tsconfig.json");
|
|
250
|
+
const engines = pick(asRecord(manifest.engines), "node");
|
|
251
|
+
out.stack.push(`${typed ? "TypeScript" : "JavaScript"} on Node.js${engines === void 0 ? "" : ` ${engines}`}, ${manager} (package.json${lockfiles.length > 0 ? `, ${lockfiles[0]}` : ""})`);
|
|
252
|
+
const dependencies = {
|
|
253
|
+
...asRecord(manifest.dependencies),
|
|
254
|
+
...asRecord(manifest.devDependencies)
|
|
255
|
+
};
|
|
256
|
+
const known = KNOWN_PACKAGES.filter(([name]) => name in dependencies).map(([, label]) => label);
|
|
257
|
+
if (known.length > 0) out.stack.push(`Libraries: ${known.join(", ")} (package.json)`);
|
|
258
|
+
const scripts = asRecord(manifest.scripts);
|
|
259
|
+
const run = (name) => manager === "yarn" ? `yarn ${name}` : `${manager} run ${name}`;
|
|
260
|
+
const add = (kind, script, command = run(script)) => {
|
|
261
|
+
if (pick(scripts, script) !== void 0) out.commands.push({
|
|
262
|
+
name: kind,
|
|
263
|
+
command,
|
|
264
|
+
source: `package.json scripts.${script}`
|
|
265
|
+
});
|
|
266
|
+
};
|
|
267
|
+
add("test", "test", manager === "yarn" ? "yarn test" : `${manager} test`);
|
|
268
|
+
add("build", "build");
|
|
269
|
+
add("lint", "lint");
|
|
270
|
+
add("typecheck", "typecheck");
|
|
271
|
+
add("typecheck", "type-check");
|
|
272
|
+
add("format", "format");
|
|
273
|
+
add("run", "dev");
|
|
274
|
+
if (pick(scripts, "dev") === void 0) add("run", "start");
|
|
275
|
+
if (lockfiles.length > 1) out.advice.push({
|
|
276
|
+
advice: `Pick one package manager (${manager}) and delete the other lockfiles.`,
|
|
277
|
+
why: `${lockfiles.join(" and ")} are all committed: two managers rewrite each other's lockfile.`
|
|
278
|
+
});
|
|
279
|
+
else if (lockfiles.length === 1) out.advice.push({
|
|
280
|
+
advice: `Install with ${manager} only.`,
|
|
281
|
+
why: `${lockfiles[0]} is committed: another manager would rewrite it and change versions.`
|
|
282
|
+
});
|
|
283
|
+
if (manifest.workspaces !== void 0 || reader.entries.has("pnpm-workspace.yaml")) out.advice.push({
|
|
284
|
+
advice: "Run a package's commands from that package, not from the root, when you touch one.",
|
|
285
|
+
why: "the repository holds several packages (workspaces): the root command runs them all."
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
async function scanPython(reader, out) {
|
|
289
|
+
const pyproject = await reader.text("pyproject.toml");
|
|
290
|
+
const requirements = await reader.text("requirements.txt");
|
|
291
|
+
const hasSetup = reader.entries.has("setup.py") || reader.entries.has("pipfile");
|
|
292
|
+
if (pyproject === void 0 && requirements === void 0 && !hasSetup) return;
|
|
293
|
+
const runner = reader.entries.has("uv.lock") ? "uv run " : reader.entries.has("poetry.lock") ? "poetry run " : "";
|
|
294
|
+
const source = pyproject !== void 0 ? "pyproject.toml" : "requirements.txt";
|
|
295
|
+
const text = `${pyproject ?? ""}\n${requirements ?? ""}`.toLowerCase();
|
|
296
|
+
out.stack.push(`Python (${source}${runner === "" ? "" : `, ${runner.trim()}`})`);
|
|
297
|
+
if (/pytest/.test(text)) out.commands.push({
|
|
298
|
+
name: "test",
|
|
299
|
+
command: `${runner}pytest`,
|
|
300
|
+
source
|
|
301
|
+
});
|
|
302
|
+
if (/\[tool\.ruff|\bruff\b/.test(text)) out.commands.push({
|
|
303
|
+
name: "lint",
|
|
304
|
+
command: `${runner}ruff check`,
|
|
305
|
+
source
|
|
306
|
+
});
|
|
307
|
+
if (/\[tool\.mypy|\bmypy\b/.test(text)) out.commands.push({
|
|
308
|
+
name: "typecheck",
|
|
309
|
+
command: `${runner}mypy .`,
|
|
310
|
+
source
|
|
311
|
+
});
|
|
312
|
+
if (runner !== "") out.advice.push({
|
|
313
|
+
advice: `Run Python tools through \`${runner.trim()}\`.`,
|
|
314
|
+
why: `${runner.startsWith("uv") ? "uv.lock" : "poetry.lock"} is committed: a bare \`python\` or \`pytest\` runs outside the project environment.`
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
function scanSimple(reader, out) {
|
|
318
|
+
const has = (name) => reader.entries.has(name);
|
|
319
|
+
if (has("cargo.toml")) {
|
|
320
|
+
out.stack.push("Rust (Cargo.toml)");
|
|
321
|
+
out.commands.push({
|
|
322
|
+
name: "test",
|
|
323
|
+
command: "cargo test",
|
|
324
|
+
source: "Cargo.toml"
|
|
325
|
+
});
|
|
326
|
+
out.commands.push({
|
|
327
|
+
name: "build",
|
|
328
|
+
command: "cargo build",
|
|
329
|
+
source: "Cargo.toml"
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
if (has("go.mod")) {
|
|
333
|
+
out.stack.push("Go (go.mod)");
|
|
334
|
+
out.commands.push({
|
|
335
|
+
name: "test",
|
|
336
|
+
command: "go test ./...",
|
|
337
|
+
source: "go.mod"
|
|
338
|
+
});
|
|
339
|
+
out.commands.push({
|
|
340
|
+
name: "build",
|
|
341
|
+
command: "go build ./...",
|
|
342
|
+
source: "go.mod"
|
|
343
|
+
});
|
|
344
|
+
out.commands.push({
|
|
345
|
+
name: "lint",
|
|
346
|
+
command: "go vet ./...",
|
|
347
|
+
source: "go.mod"
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
if (has("pom.xml")) {
|
|
351
|
+
out.stack.push("Java (pom.xml)");
|
|
352
|
+
out.commands.push({
|
|
353
|
+
name: "test",
|
|
354
|
+
command: "mvn test",
|
|
355
|
+
source: "pom.xml"
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
const gradle = ["build.gradle", "build.gradle.kts"].find(has);
|
|
359
|
+
if (gradle !== void 0) {
|
|
360
|
+
const wrapper = has("gradlew") ? "./gradlew" : "gradle";
|
|
361
|
+
out.stack.push(`JVM, Gradle (${gradle})`);
|
|
362
|
+
out.commands.push({
|
|
363
|
+
name: "test",
|
|
364
|
+
command: `${wrapper} test`,
|
|
365
|
+
source: gradle
|
|
366
|
+
});
|
|
367
|
+
out.commands.push({
|
|
368
|
+
name: "build",
|
|
369
|
+
command: `${wrapper} build`,
|
|
370
|
+
source: gradle
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
const dotnet = [...reader.entries].find((name) => /\.(?:sln|slnx|csproj|fsproj)$/.test(name));
|
|
374
|
+
if (dotnet !== void 0) {
|
|
375
|
+
out.stack.push(`.NET (${dotnet})`);
|
|
376
|
+
out.commands.push({
|
|
377
|
+
name: "test",
|
|
378
|
+
command: "dotnet test",
|
|
379
|
+
source: dotnet
|
|
380
|
+
});
|
|
381
|
+
out.commands.push({
|
|
382
|
+
name: "build",
|
|
383
|
+
command: "dotnet build",
|
|
384
|
+
source: dotnet
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
if (has("gemfile")) {
|
|
388
|
+
out.stack.push("Ruby (Gemfile)");
|
|
389
|
+
if (has(".rspec")) out.commands.push({
|
|
390
|
+
name: "test",
|
|
391
|
+
command: "bundle exec rspec",
|
|
392
|
+
source: ".rspec"
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
if (has("mix.exs")) {
|
|
396
|
+
out.stack.push("Elixir (mix.exs)");
|
|
397
|
+
out.commands.push({
|
|
398
|
+
name: "test",
|
|
399
|
+
command: "mix test",
|
|
400
|
+
source: "mix.exs"
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
if (has("package.swift")) {
|
|
404
|
+
out.stack.push("Swift (Package.swift)");
|
|
405
|
+
out.commands.push({
|
|
406
|
+
name: "test",
|
|
407
|
+
command: "swift test",
|
|
408
|
+
source: "Package.swift"
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
if (has("pubspec.yaml")) out.stack.push("Dart (pubspec.yaml)");
|
|
412
|
+
}
|
|
413
|
+
async function scanComposer(reader, out) {
|
|
414
|
+
const raw = await reader.text("composer.json");
|
|
415
|
+
if (raw === void 0) return;
|
|
416
|
+
out.stack.push("PHP (composer.json)");
|
|
417
|
+
try {
|
|
418
|
+
if (asRecord(asRecord(JSON.parse(raw))?.scripts)?.test !== void 0) out.commands.push({
|
|
419
|
+
name: "test",
|
|
420
|
+
command: "composer test",
|
|
421
|
+
source: "composer.json scripts.test"
|
|
422
|
+
});
|
|
423
|
+
} catch {}
|
|
424
|
+
}
|
|
425
|
+
/** The targets of a Makefile the profile names (a target is a command only when it is written). */
|
|
426
|
+
async function scanMake(reader, out) {
|
|
427
|
+
const text = await reader.text("Makefile");
|
|
428
|
+
if (text === void 0) return;
|
|
429
|
+
const targets = new Set([...text.matchAll(/^([a-z][\w-]*)\s*:(?!=)/gm)].map((match) => match[1]));
|
|
430
|
+
for (const [kind, target] of [
|
|
431
|
+
["test", "test"],
|
|
432
|
+
["build", "build"],
|
|
433
|
+
["lint", "lint"],
|
|
434
|
+
["format", "fmt"],
|
|
435
|
+
["format", "format"]
|
|
436
|
+
]) if (targets.has(target) && !out.commands.some((command) => command.name === kind)) out.commands.push({
|
|
437
|
+
name: kind,
|
|
438
|
+
command: `make ${target}`,
|
|
439
|
+
source: `Makefile ${target}:`
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
const TOOLING = [
|
|
443
|
+
[
|
|
444
|
+
["biome.json", "biome.jsonc"],
|
|
445
|
+
"Biome formats and lints",
|
|
446
|
+
"its config is committed"
|
|
447
|
+
],
|
|
448
|
+
[
|
|
449
|
+
[
|
|
450
|
+
".prettierrc",
|
|
451
|
+
".prettierrc.json",
|
|
452
|
+
".prettierrc.js",
|
|
453
|
+
"prettier.config.js"
|
|
454
|
+
],
|
|
455
|
+
"Prettier formats",
|
|
456
|
+
"its config is committed"
|
|
457
|
+
],
|
|
458
|
+
[
|
|
459
|
+
[
|
|
460
|
+
".eslintrc",
|
|
461
|
+
".eslintrc.json",
|
|
462
|
+
".eslintrc.js",
|
|
463
|
+
"eslint.config.js",
|
|
464
|
+
"eslint.config.mjs"
|
|
465
|
+
],
|
|
466
|
+
"ESLint lints",
|
|
467
|
+
"its config is committed"
|
|
468
|
+
],
|
|
469
|
+
[
|
|
470
|
+
["rustfmt.toml", ".rustfmt.toml"],
|
|
471
|
+
"rustfmt formats",
|
|
472
|
+
"its config is committed"
|
|
473
|
+
],
|
|
474
|
+
[
|
|
475
|
+
[".golangci.yml", ".golangci.yaml"],
|
|
476
|
+
"golangci-lint lints",
|
|
477
|
+
"its config is committed"
|
|
478
|
+
],
|
|
479
|
+
[
|
|
480
|
+
[".rubocop.yml"],
|
|
481
|
+
"RuboCop lints",
|
|
482
|
+
"its config is committed"
|
|
483
|
+
],
|
|
484
|
+
[
|
|
485
|
+
[".editorconfig"],
|
|
486
|
+
"EditorConfig sets indentation and line endings",
|
|
487
|
+
".editorconfig is committed"
|
|
488
|
+
]
|
|
489
|
+
];
|
|
490
|
+
const CI_FILES = [
|
|
491
|
+
".gitlab-ci.yml",
|
|
492
|
+
"azure-pipelines.yml",
|
|
493
|
+
"jenkinsfile",
|
|
494
|
+
".travis.yml",
|
|
495
|
+
"bitbucket-pipelines.yml",
|
|
496
|
+
"appveyor.yml"
|
|
497
|
+
];
|
|
498
|
+
async function scanConventions(reader, files, out) {
|
|
499
|
+
for (const [names, label, why] of TOOLING) {
|
|
500
|
+
const found = names.find((name) => reader.entries.has(name.toLowerCase()));
|
|
501
|
+
if (found === void 0) continue;
|
|
502
|
+
out.conventions.push(`${label} (${found}).`);
|
|
503
|
+
if (label.startsWith("Biome") || label.startsWith("Prettier") || label.startsWith("rustfmt")) out.advice.push({
|
|
504
|
+
advice: `Format with the project's formatter, not by hand.`,
|
|
505
|
+
why: `${found}: ${why}, and a style-only diff is noise in a review.`
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
for (const pinned of [
|
|
509
|
+
".nvmrc",
|
|
510
|
+
".node-version",
|
|
511
|
+
".python-version",
|
|
512
|
+
".tool-versions"
|
|
513
|
+
]) {
|
|
514
|
+
if (!reader.entries.has(pinned)) continue;
|
|
515
|
+
const value = (await reader.text(pinned))?.split("\n")[0]?.trim();
|
|
516
|
+
out.conventions.push(`Runtime version pinned: ${value ?? "see file"} (${pinned}).`);
|
|
517
|
+
}
|
|
518
|
+
let workflows = [];
|
|
519
|
+
try {
|
|
520
|
+
workflows = (await files.listEntries?.(".github/workflows") ?? []).filter((name) => /\.ya?ml$/i.test(name));
|
|
521
|
+
} catch {
|
|
522
|
+
workflows = [];
|
|
523
|
+
}
|
|
524
|
+
if (workflows.length > 0) out.conventions.push(`CI: GitHub Actions (${workflows.slice(0, 4).join(", ")}).`);
|
|
525
|
+
const ci = CI_FILES.find((name) => reader.entries.has(name));
|
|
526
|
+
if (ci !== void 0) out.conventions.push(`CI: ${ci}.`);
|
|
527
|
+
if (reader.entries.has("dockerfile") || reader.entries.has("docker-compose.yml")) out.conventions.push("Containers: Dockerfile or docker-compose.yml at the root.");
|
|
528
|
+
if (reader.entries.has("tsconfig.json")) out.conventions.push("TypeScript: tsconfig.json at the root.");
|
|
529
|
+
}
|
|
530
|
+
/** Reads the product's root and says what it is. Never throws: an unreadable file says nothing. */
|
|
531
|
+
async function scanProject(files) {
|
|
532
|
+
const out = {
|
|
533
|
+
stack: [],
|
|
534
|
+
commands: [],
|
|
535
|
+
conventions: [],
|
|
536
|
+
layout: [],
|
|
537
|
+
advice: []
|
|
538
|
+
};
|
|
539
|
+
let names = [];
|
|
540
|
+
try {
|
|
541
|
+
names = await files.listEntries?.("") ?? [];
|
|
542
|
+
} catch {
|
|
543
|
+
names = [];
|
|
544
|
+
}
|
|
545
|
+
const byLower = lower(names);
|
|
546
|
+
const reader = {
|
|
547
|
+
entries: new Set(byLower.keys()),
|
|
548
|
+
async text(name) {
|
|
549
|
+
const actual = byLower.get(name.toLowerCase());
|
|
550
|
+
if (actual === void 0) return void 0;
|
|
551
|
+
try {
|
|
552
|
+
const text = await files.readText(actual);
|
|
553
|
+
return text.length > MAX_MANIFEST ? void 0 : text.replace(/\r\n/g, "\n");
|
|
554
|
+
} catch {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
await scanNode(reader, out);
|
|
560
|
+
await scanPython(reader, out);
|
|
561
|
+
scanSimple(reader, out);
|
|
562
|
+
await scanComposer(reader, out);
|
|
563
|
+
await scanMake(reader, out);
|
|
564
|
+
await scanConventions(reader, files, out);
|
|
565
|
+
const folders = [];
|
|
566
|
+
for (const name of names) {
|
|
567
|
+
if (name.includes(".") || name.startsWith("_") || NOT_LAYOUT.has(name.toLowerCase())) continue;
|
|
568
|
+
if (folders.length >= 30) break;
|
|
569
|
+
try {
|
|
570
|
+
if ((await files.listEntries?.(name) ?? []).length > 0) folders.push(name);
|
|
571
|
+
} catch {}
|
|
572
|
+
}
|
|
573
|
+
const tests = folders.filter((name) => TEST_FOLDERS.has(name.toLowerCase()));
|
|
574
|
+
const product = folders.filter((name) => !TEST_FOLDERS.has(name.toLowerCase()));
|
|
575
|
+
if (product.length > 0) out.layout.push(`Folders: ${product.slice(0, MAX_LAYOUT).join(", ")}.`);
|
|
576
|
+
if (tests.length > 0) out.layout.push(`Tests live in: ${tests.join(", ")}.`);
|
|
577
|
+
if (!out.commands.some((command) => command.name === "test") && out.stack.length > 0) out.advice.push({
|
|
578
|
+
advice: "No test command was found: ask the creator for it before inventing one.",
|
|
579
|
+
why: "no manifest or Makefile of the root defines it; a guessed command proves nothing."
|
|
580
|
+
});
|
|
581
|
+
return out;
|
|
582
|
+
}
|
|
583
|
+
// ../../packages/core/dist/project-profile/project-command.js
|
|
584
|
+
const invalid = (reason) => new AgentPluggedError(ErrorCode.INVALID_ARGUMENTS, "error.invalid_arguments", { reason });
|
|
585
|
+
async function readText(context, path) {
|
|
586
|
+
try {
|
|
587
|
+
return await context.files.exists(path) ? await context.files.readText(path) : void 0;
|
|
588
|
+
} catch {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
/** The single sources the team declared (the table of the profile), empty without a profile. */
|
|
593
|
+
async function singleSourcesOf(context) {
|
|
594
|
+
const text = await readText(context, PROFILE_PATH);
|
|
595
|
+
return text === void 0 ? [] : parseKept(text).sources;
|
|
596
|
+
}
|
|
597
|
+
/** The profile file for a scan, the parts people wrote kept from `current`. */
|
|
598
|
+
function profileFile(scan, current) {
|
|
599
|
+
const kept = current === void 0 ? {
|
|
600
|
+
sources: [],
|
|
601
|
+
acceptance: "",
|
|
602
|
+
notes: ""
|
|
603
|
+
} : parseKept(current);
|
|
604
|
+
return renderProfile(scan, kept);
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* `docs profile [refresh]`: the project profile. Without `refresh`, the file as it would be written
|
|
608
|
+
* now (and whether the file on disk still says the same); with it, the file is written. The profile
|
|
609
|
+
* is read from the root files of the product, so it is never invented.
|
|
610
|
+
*/
|
|
611
|
+
async function showProfile(context, words) {
|
|
612
|
+
if (words[0] === "accept") return declareAcceptance(context, words.slice(1));
|
|
613
|
+
if (words.length > 1 || words.length === 1 && words[0] !== "refresh") return invalid(`docs profile ${words.join(" ")} (profile [refresh], or profile accept <preview|staging|local> [how])`);
|
|
614
|
+
const current = await readText(context, PROFILE_PATH);
|
|
615
|
+
const fresh = profileFile(await scanProject(context.files), current);
|
|
616
|
+
const same = current !== void 0 && current.replace(/\r\n/g, "\n") === fresh;
|
|
617
|
+
if (words[0] === "refresh") {
|
|
618
|
+
if (!same) await context.files.writeText(PROFILE_PATH, fresh);
|
|
619
|
+
return { profile: {
|
|
620
|
+
path: PROFILE_PATH,
|
|
621
|
+
written: !same,
|
|
622
|
+
state: current === void 0 ? "created" : same ? "current" : "refreshed",
|
|
623
|
+
lines: fresh.split("\n")
|
|
624
|
+
} };
|
|
625
|
+
}
|
|
626
|
+
return { profile: {
|
|
627
|
+
path: PROFILE_PATH,
|
|
628
|
+
written: false,
|
|
629
|
+
state: current === void 0 ? "absent" : same ? "current" : "stale",
|
|
630
|
+
lines: fresh.split("\n")
|
|
631
|
+
} };
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* `docs profile accept <preview|staging|local> [how…]`: where the acceptance run happens, decided with the
|
|
635
|
+
* creator and kept in the profile (a refresh never overwrites it).
|
|
636
|
+
*/
|
|
637
|
+
async function declareAcceptance(context, words) {
|
|
638
|
+
const [place, ...how] = words;
|
|
639
|
+
if (!ACCEPTANCE_PLACES.some((known) => known === place)) return invalid("docs profile accept <preview|staging|local> [how to reach it]");
|
|
640
|
+
const current = await readText(context, PROFILE_PATH);
|
|
641
|
+
const kept = current === void 0 ? {
|
|
642
|
+
sources: [],
|
|
643
|
+
acceptance: "",
|
|
644
|
+
notes: ""
|
|
645
|
+
} : parseKept(current);
|
|
646
|
+
const fresh = renderProfile(await scanProject(context.files), {
|
|
647
|
+
...kept,
|
|
648
|
+
acceptance: acceptanceText(place, how.join(" "))
|
|
649
|
+
});
|
|
650
|
+
await context.files.writeText(PROFILE_PATH, fresh);
|
|
651
|
+
return { profile: {
|
|
652
|
+
path: PROFILE_PATH,
|
|
653
|
+
written: true,
|
|
654
|
+
state: "refreshed",
|
|
655
|
+
lines: fresh.split("\n")
|
|
656
|
+
} };
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* `docs bricks <words>`: does something like this already exist? The bricks (functions and types)
|
|
660
|
+
* whose names carry the words, best first, and the single source that owns the concept if one is
|
|
661
|
+
* declared. `docs bricks own <concept> <path> [rule…]` declares a single source: the concept then
|
|
662
|
+
* has one home, and a second one added by a story is flagged at its review.
|
|
663
|
+
*/
|
|
664
|
+
async function findInRegistry(context, words) {
|
|
665
|
+
if (words[0] === "own") return declareSource(context, words.slice(1));
|
|
666
|
+
if (words.length === 0) return invalid("docs bricks <words> | docs bricks own <concept> <path>");
|
|
667
|
+
const sources = await singleSourcesOf(context);
|
|
668
|
+
const named = matchingSources(sources, words);
|
|
669
|
+
const loaded = await loadCodeMap(context);
|
|
670
|
+
if (typeof loaded === "string") return { bricks: {
|
|
671
|
+
query: words,
|
|
672
|
+
hits: [],
|
|
673
|
+
sources: named,
|
|
674
|
+
unavailable: loaded
|
|
675
|
+
} };
|
|
676
|
+
const bricks = bricksOfMap(loaded);
|
|
677
|
+
return { bricks: {
|
|
678
|
+
query: words,
|
|
679
|
+
hits: findBricks(bricks, words).map(({ brick }) => ({
|
|
680
|
+
name: brick.name,
|
|
681
|
+
kind: brick.kind,
|
|
682
|
+
path: brick.path,
|
|
683
|
+
line: brick.line,
|
|
684
|
+
sig: brick.sig
|
|
685
|
+
})),
|
|
686
|
+
sources: named,
|
|
687
|
+
searched: {
|
|
688
|
+
total: bricks.length,
|
|
689
|
+
commit: loaded.commit.slice(0, 12)
|
|
690
|
+
}
|
|
691
|
+
} };
|
|
692
|
+
}
|
|
693
|
+
async function declareSource(context, words) {
|
|
694
|
+
const [concept, owner, ...rule] = words;
|
|
695
|
+
if (concept === void 0 || owner === void 0 || concept.length < 2) return invalid("docs bricks own <concept> <path> [rule…]");
|
|
696
|
+
const bare = owner.replaceAll("\\", "/").split("/").filter((part) => part !== "" && part !== ".").join("/");
|
|
697
|
+
if (bare === "" || bare.startsWith("/") || bare.split("/").includes("..")) return invalid(`docs bricks own ${concept} ${owner} (a path of the product)`);
|
|
698
|
+
if (!await context.files.exists(bare)) return invalid(`docs bricks own ${concept} ${owner} (no such file or folder)`);
|
|
699
|
+
const source = {
|
|
700
|
+
concept: concept.replace(/[|\r\n]+/g, " ").trim(),
|
|
701
|
+
owner: (await context.files.listEntries?.(bare) ?? []).length > 0 ? `${bare}/` : bare,
|
|
702
|
+
rule: rule.join(" ").slice(0, 160)
|
|
703
|
+
};
|
|
704
|
+
const current = await readText(context, PROFILE_PATH);
|
|
705
|
+
const scan = await scanProject(context.files);
|
|
706
|
+
const kept = withSource(current === void 0 ? {
|
|
707
|
+
sources: [],
|
|
708
|
+
acceptance: "",
|
|
709
|
+
notes: ""
|
|
710
|
+
} : parseKept(current), source);
|
|
711
|
+
await context.files.writeText(PROFILE_PATH, renderProfile(scan, kept));
|
|
712
|
+
return { bricks: {
|
|
713
|
+
query: [concept],
|
|
714
|
+
hits: [],
|
|
715
|
+
sources: [source],
|
|
716
|
+
declared: source
|
|
717
|
+
} };
|
|
718
|
+
}
|
|
719
|
+
/** Longest list of each kind in the brief. */
|
|
720
|
+
const BRIEF_ROWS = 6;
|
|
721
|
+
/**
|
|
722
|
+
* The profile brief for a story: read from the root files now (a stale file never misleads) with the
|
|
723
|
+
* single sources of the profile. Undefined when the product has no stack to describe yet.
|
|
724
|
+
*/
|
|
725
|
+
async function profileBrief(context) {
|
|
726
|
+
try {
|
|
727
|
+
const scan = await scanProject(context.files);
|
|
728
|
+
if (scan.stack.length === 0 && scan.commands.length === 0) return void 0;
|
|
729
|
+
const sources = await singleSourcesOf(context);
|
|
730
|
+
return {
|
|
731
|
+
path: PROFILE_PATH,
|
|
732
|
+
stack: scan.stack.slice(0, BRIEF_ROWS).map((row) => row.slice(0, 140)),
|
|
733
|
+
commands: scan.commands.slice(0, 12).map((c) => `${c.name}: ${c.command}`),
|
|
734
|
+
single_sources: sources.slice(0, 10).map((s) => `${s.concept} -> ${s.owner}${s.rule === "" ? "" : ` (${s.rule})`}`),
|
|
735
|
+
advice: scan.advice.slice(0, 3).map((a) => `${a.advice} Why: ${a.why}`.slice(0, 220)),
|
|
736
|
+
ask: "npx agentplugged docs bricks <words>"
|
|
737
|
+
};
|
|
738
|
+
} catch {
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* `docs map <request…>`: the map of the code for a piece of work, the same one `start_story` hands
|
|
744
|
+
* the agent for a story's sheet: the files that matter for the request and the signatures they
|
|
745
|
+
* export, within the same budget. Without a request, the files the rest of the code leans on.
|
|
746
|
+
*/
|
|
747
|
+
async function showRepoMap(context, words) {
|
|
748
|
+
const loaded = await loadCodeMap(context);
|
|
749
|
+
if (typeof loaded === "string") return { map: {
|
|
750
|
+
tokens: 0,
|
|
751
|
+
files: 0,
|
|
752
|
+
symbols: 0,
|
|
753
|
+
omitted_files: 0,
|
|
754
|
+
unavailable: loaded
|
|
755
|
+
} };
|
|
756
|
+
return { map: repoMapOf(bricksOfMap(loaded), words.join(" "), 1500) ?? {
|
|
757
|
+
tokens: 0,
|
|
758
|
+
files: 0,
|
|
759
|
+
symbols: 0,
|
|
760
|
+
omitted_files: 0
|
|
761
|
+
} };
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* The map of the repository for a request (a story's sheet), read from the registry of the commit
|
|
765
|
+
* the folder is on: the files that matter and their exported signatures, within the budget.
|
|
766
|
+
* Undefined when the code cannot be read or holds nothing to show; never a failure.
|
|
767
|
+
*/
|
|
768
|
+
async function repoMapFor(context, request, budget = MAP_TOKENS) {
|
|
769
|
+
try {
|
|
770
|
+
const loaded = await loadCodeMap(context);
|
|
771
|
+
return typeof loaded === "string" ? void 0 : repoMapOf(bricksOfMap(loaded), request, budget);
|
|
772
|
+
} catch {
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
export { showProfile as a, scanProject as c, mapTokens as d, repoMapOf as f, repoMapFor as i, MAP_MAX_TOKENS as l, profileBrief as n, showRepoMap as o, profileFile as r, singleSourcesOf as s, findInRegistry as t, MAP_TOKENS as u };
|