@rt-tools/agent-kit 0.2.0 → 0.4.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 +235 -18
- package/assets/agents/business-analyst.md +74 -0
- package/assets/agents/project-manager.md +70 -0
- package/assets/agents/qa-engineer.md +72 -0
- package/assets/agents/skill-curator.md +110 -0
- package/assets/agents/spec-critic.md +44 -0
- package/assets/agents/spec-writer.md +50 -0
- package/assets/checks/board.github.mjs +286 -0
- package/assets/checks/check-board.github.mjs +188 -0
- package/assets/checks/check-doc-paths.mjs +163 -0
- package/assets/checks/check-dupes.mjs +277 -0
- package/assets/checks/check-lib-layers.mjs +573 -0
- package/assets/checks/check-reuse.mjs +208 -0
- package/assets/checks/check-schema-drift.mjs +186 -0
- package/assets/checks/check-specs.mjs +1007 -0
- package/assets/checks/check-styles.mjs +109 -0
- package/assets/checks/rt-kit-checks.config.mjs +134 -0
- package/assets/checks/task-new.github.mjs +198 -0
- package/assets/commands/skill-curator.md +70 -0
- package/assets/defaults/gate-map.sh +100 -0
- package/assets/defaults/project.sh +179 -0
- package/assets/hooks/browser-device-id.sh +20 -0
- package/assets/hooks/browser-guard-device-id.sh +28 -0
- package/assets/hooks/browser-guard-no-asking.sh +27 -0
- package/assets/hooks/browser-guard-no-listing.sh +18 -0
- package/assets/hooks/browser-guard-no-other-drivers.sh +79 -0
- package/assets/hooks/browser-guard-require-select.sh +54 -0
- package/assets/hooks/commit-msg.sh +26 -0
- package/assets/hooks/constitution-index.sh +43 -0
- package/assets/hooks/dev-server-guard.sh +115 -0
- package/assets/hooks/docs-guard.sh +282 -0
- package/assets/hooks/git-guard-delivery.sh +167 -0
- package/assets/hooks/git-guard-main.sh +73 -0
- package/assets/hooks/git-guard-push-tests.sh +94 -0
- package/assets/hooks/glossary-load.sh +23 -0
- package/assets/hooks/lint-after-edit.sh +219 -0
- package/assets/hooks/qa-dataid-guard.sh +121 -0
- package/assets/hooks/reuse-first-guard.sh +154 -0
- package/assets/hooks/skill-gate-rearm.sh +23 -0
- package/assets/hooks/skill-gate.sh +128 -0
- package/assets/hooks/skill-loaded.sh +21 -0
- package/assets/hooks/sql-guard.sh +679 -0
- package/assets/hooks/task-context-load.sh +100 -0
- package/assets/hooks/task-flow-guard.sh +107 -0
- package/assets/laws/{access.md → application/access.md} +1 -4
- package/assets/laws/{locales.md → application/locales.md} +1 -3
- package/assets/laws/application/money.md +41 -0
- package/assets/laws/application/ownership.md +32 -0
- package/assets/laws/{search-visibility.md → application/search-visibility.md} +1 -1
- package/assets/laws/code-structure.md +7 -6
- package/assets/laws/delivery.md +53 -3
- package/assets/laws/entity-editing.md +49 -55
- package/assets/laws/entity-models.md +4 -14
- package/assets/laws/frontend-application.md +5 -5
- package/assets/laws/lib-imports.md +14 -1
- package/assets/laws/lists.md +33 -0
- package/assets/laws/navigation.md +40 -0
- package/assets/laws/project-documentation.md +17 -8
- package/assets/laws/reuse-first.md +26 -21
- package/assets/laws/shared-code.md +13 -1
- package/assets/laws/verifiability.md +17 -1
- package/assets/laws/work-conduct.md +48 -0
- package/assets/patterns/admin-lists-screen.md +131 -0
- package/assets/patterns/admin-nav-item.md +71 -0
- package/assets/patterns/angular-patterns-state.md +101 -0
- package/assets/patterns/api-layer-pair.md +88 -0
- package/assets/patterns/browser-verification-measure.md +86 -0
- package/assets/patterns/browser-verification-stand.md +143 -0
- package/assets/patterns/component-structure-new.md +99 -0
- package/assets/patterns/dependencies-upgrade.md +65 -0
- package/assets/patterns/doc-style-sweep.md +137 -0
- package/assets/patterns/doc-style-write.md +109 -0
- package/assets/patterns/entity-aside.md +136 -0
- package/assets/patterns/entity-models-new.md +124 -0
- package/assets/patterns/entity-store.md +91 -0
- package/assets/patterns/git-workflow-commit.azure.md +259 -0
- package/assets/patterns/git-workflow-commit.github.md +333 -0
- package/assets/patterns/git-workflow-commit.gitlab.md +283 -0
- package/assets/patterns/git-workflow-merge.md +99 -0
- package/assets/patterns/git-workflow-migration.md +88 -0
- package/assets/patterns/git-workflow-restart.md +49 -0
- package/assets/patterns/lib-layers-move.md +95 -0
- package/assets/patterns/lib-layers-new.md +82 -0
- package/assets/patterns/ownership-scope-resolve.md +69 -0
- package/assets/patterns/permissions-procedure.md +71 -0
- package/assets/patterns/platform-access-di.md +84 -0
- package/assets/patterns/pricing-quote.md +71 -0
- package/assets/patterns/reuse-first-extend.md +73 -0
- package/assets/patterns/seo-page.md +104 -0
- package/assets/patterns/seo-verify.md +83 -0
- package/assets/patterns/shared-code-new.md +86 -0
- package/assets/patterns/spec-driven-domain.md +107 -0
- package/assets/patterns/spec-driven-rule.md +127 -0
- package/assets/patterns/styling-bem-component.md +88 -0
- package/assets/patterns/styling-bem-layout.md +73 -0
- package/assets/patterns/task-flow-close.md +90 -0
- package/assets/patterns/task-flow-resume.md +94 -0
- package/assets/patterns/task-flow-start.md +117 -0
- package/assets/patterns/testing-e2e.md +92 -0
- package/assets/patterns/testing-unit.md +117 -0
- package/assets/patterns/translations-key.md +64 -0
- package/assets/patterns/ts-procedure.md +65 -0
- package/assets/rules/angular-patterns.md +71 -0
- package/assets/rules/api-layer.md +71 -0
- package/assets/rules/browser-verification.md +87 -0
- package/assets/rules/component-structure.md +64 -0
- package/assets/rules/dependencies.md +66 -0
- package/assets/rules/doc-style.md +103 -0
- package/assets/rules/entity-conventions.md +78 -0
- package/assets/rules/entity-models.md +70 -0
- package/assets/rules/git-workflow.azure.md +116 -0
- package/assets/rules/git-workflow.github.md +123 -0
- package/assets/rules/git-workflow.gitlab.md +113 -0
- package/assets/rules/lib-layers.md +80 -0
- package/assets/rules/lists.md +73 -0
- package/assets/rules/navigation.md +78 -0
- package/assets/rules/ownership-scope.md +63 -0
- package/assets/rules/permissions.md +70 -0
- package/assets/rules/platform-access.md +77 -0
- package/assets/rules/pricing.md +64 -0
- package/assets/rules/reuse-first.md +83 -0
- package/assets/rules/seo.md +71 -0
- package/assets/rules/shared-code.md +70 -0
- package/assets/rules/spec-driven.md +135 -0
- package/assets/rules/styling-bem.md +74 -0
- package/assets/rules/task-flow.md +110 -0
- package/assets/rules/testing.md +100 -0
- package/assets/rules/translations.md +69 -0
- package/assets/rules/typescript-conventions.md +76 -0
- package/assets/skills/agent-kit.md +81 -0
- package/assets/skills/write-a-skill.md +108 -0
- package/assets/templates/gate-map.sh +45 -0
- package/assets/templates/implementation.md +44 -0
- package/assets/templates/pattern.md +5 -1
- package/assets/templates/project.sh +54 -0
- package/assets/templates/rule.md +12 -23
- package/assets/variants.json +20 -0
- package/assets/workflows/feature.js +134 -0
- package/assets/workflows/plan.js +150 -0
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +78 -5
- package/bin/agent-kit.js.map +1 -1
- package/bin/prompt.d.ts +5 -0
- package/bin/prompt.d.ts.map +1 -1
- package/bin/prompt.js +19 -7
- package/bin/prompt.js.map +1 -1
- package/index.d.ts +1 -0
- package/index.d.ts.map +1 -1
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/lib/assets.d.ts +14 -1
- package/lib/assets.d.ts.map +1 -1
- package/lib/assets.js +23 -2
- package/lib/assets.js.map +1 -1
- package/lib/catalog.d.ts +52 -5
- package/lib/catalog.d.ts.map +1 -1
- package/lib/catalog.js +104 -16
- package/lib/catalog.js.map +1 -1
- package/lib/commands.d.ts +22 -1
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +218 -11
- package/lib/commands.js.map +1 -1
- package/lib/companion.d.ts +57 -0
- package/lib/companion.d.ts.map +1 -0
- package/lib/companion.js +60 -0
- package/lib/companion.js.map +1 -0
- package/lib/config.d.ts +42 -2
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +60 -2
- package/lib/config.js.map +1 -1
- package/lib/freshness.d.ts +14 -0
- package/lib/freshness.d.ts.map +1 -0
- package/lib/freshness.js +116 -0
- package/lib/freshness.js.map +1 -0
- package/lib/hooks-map.d.ts +24 -0
- package/lib/hooks-map.d.ts.map +1 -0
- package/lib/hooks-map.js +72 -0
- package/lib/hooks-map.js.map +1 -0
- package/lib/integrity.d.ts +36 -0
- package/lib/integrity.d.ts.map +1 -0
- package/lib/integrity.js +44 -0
- package/lib/integrity.js.map +1 -0
- package/lib/picker.d.ts +11 -1
- package/lib/picker.d.ts.map +1 -1
- package/lib/picker.js +44 -6
- package/lib/picker.js.map +1 -1
- package/lib/stamp.d.ts +2 -5
- package/lib/stamp.d.ts.map +1 -1
- package/lib/stamp.js +25 -10
- package/lib/stamp.js.map +1 -1
- package/lib/sync.d.ts +29 -0
- package/lib/sync.d.ts.map +1 -1
- package/lib/sync.js +78 -4
- package/lib/sync.js.map +1 -1
- package/lib/variants.d.ts +44 -0
- package/lib/variants.d.ts.map +1 -0
- package/lib/variants.js +82 -0
- package/lib/variants.js.map +1 -0
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.4.0.tgz +0 -0
- package/assets/laws/admin-lists.md +0 -35
- package/assets/laws/admin-navigation.md +0 -38
- package/rt-tools-agent-kit-0.2.0.tgz +0 -0
package/lib/picker.d.ts
CHANGED
|
@@ -16,6 +16,11 @@ export interface IPickerState {
|
|
|
16
16
|
readonly choices: readonly IChoice[];
|
|
17
17
|
readonly cursor: number;
|
|
18
18
|
readonly chosen: ReadonlySet<string>;
|
|
19
|
+
/**
|
|
20
|
+
* Выбирается ровно один. Вид ресурса галочками не набирается: репозиторий лежит либо в
|
|
21
|
+
* одном месте, либо в другом, и «оба» здесь означало бы два правила поставки в одном дереве.
|
|
22
|
+
*/
|
|
23
|
+
readonly single: boolean;
|
|
19
24
|
/** Выбор окончен, `chosen` — ответ. */
|
|
20
25
|
readonly done: boolean;
|
|
21
26
|
/** Выбор брошен: ответа нет, и подставлять вместо него умолчание нельзя. */
|
|
@@ -23,6 +28,11 @@ export interface IPickerState {
|
|
|
23
28
|
}
|
|
24
29
|
/** Первым предложением взято всё: отказ от закона — решение, и принимать его должен человек. */
|
|
25
30
|
export declare function pickerOf(choices: readonly IChoice[]): IPickerState;
|
|
31
|
+
/**
|
|
32
|
+
* Выбор одного из. Отмечено то, что под курсором, и переезд курсора переносит отметку: так
|
|
33
|
+
* ответ есть в любой момент, и enter отвечает без единого нажатия пробела.
|
|
34
|
+
*/
|
|
35
|
+
export declare function singlePickerOf(choices: readonly IChoice[]): IPickerState;
|
|
26
36
|
export declare const KEY_UP: string;
|
|
27
37
|
export declare const KEY_DOWN: string;
|
|
28
38
|
export declare const KEY_ESCAPE: string;
|
|
@@ -42,6 +52,6 @@ export declare function keysOf(chunk: string): readonly string[];
|
|
|
42
52
|
export declare function pressKey(state: IPickerState, key: string): IPickerState;
|
|
43
53
|
/** Кусок потока целиком: разобрать на клавиши и провести состояние через каждую. */
|
|
44
54
|
export declare const pressChunk: (state: IPickerState, chunk: string) => IPickerState;
|
|
45
|
-
/** Разметка выбиралки строками. Курсор — стрелкой, выбранное — крестиком в скобках. */
|
|
55
|
+
/** Разметка выбиралки строками. Курсор — стрелкой, выбранное — крестиком или точкой в скобках. */
|
|
46
56
|
export declare function renderPicker(state: IPickerState, question: string): readonly string[];
|
|
47
57
|
//# sourceMappingURL=picker.d.ts.map
|
package/lib/picker.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"picker.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/picker.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,OAAO;IACpB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,OAAO,EAAE,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC/B;AAED,gGAAgG;AAChG,wBAAgB,QAAQ,CAAC,OAAO,EAAE,SAAS,OAAO,EAAE,GAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"picker.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/picker.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,OAAO;IACpB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,OAAO,EAAE,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC/B;AAED,gGAAgG;AAChG,wBAAgB,QAAQ,CAAC,OAAO,EAAE,SAAS,OAAO,EAAE,GAAG,YAAY,CASlE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,OAAO,EAAE,GAAG,YAAY,CASxE;AAUD,eAAO,MAAM,MAAM,EAAE,MAAsB,CAAC;AAC5C,eAAO,MAAM,QAAQ,EAAE,MAAsB,CAAC;AAC9C,eAAO,MAAM,UAAU,EAAE,MAAe,CAAC;AACzC,eAAO,MAAM,UAAU,EAAE,MAA+B,CAAC;AA8CzD;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAcvD;AAED,uFAAuF;AACvF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,CAwBvE;AAED,oFAAoF;AACpF,eAAO,MAAM,UAAU,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,KAAK,YACxB,CAAC;AAK1C,kGAAkG;AAClG,wBAAgB,YAAY,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAiBrF"}
|
package/lib/picker.js
CHANGED
|
@@ -7,7 +7,28 @@
|
|
|
7
7
|
*/
|
|
8
8
|
/** Первым предложением взято всё: отказ от закона — решение, и принимать его должен человек. */
|
|
9
9
|
export function pickerOf(choices) {
|
|
10
|
-
return {
|
|
10
|
+
return {
|
|
11
|
+
choices,
|
|
12
|
+
cursor: 0,
|
|
13
|
+
chosen: new Set(choices.map((choice) => choice.id)),
|
|
14
|
+
single: false,
|
|
15
|
+
done: false,
|
|
16
|
+
cancelled: false,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Выбор одного из. Отмечено то, что под курсором, и переезд курсора переносит отметку: так
|
|
21
|
+
* ответ есть в любой момент, и enter отвечает без единого нажатия пробела.
|
|
22
|
+
*/
|
|
23
|
+
export function singlePickerOf(choices) {
|
|
24
|
+
return {
|
|
25
|
+
choices,
|
|
26
|
+
cursor: 0,
|
|
27
|
+
chosen: new Set(choices.slice(0, 1).map((choice) => choice.id)),
|
|
28
|
+
single: true,
|
|
29
|
+
done: false,
|
|
30
|
+
cancelled: false,
|
|
31
|
+
};
|
|
11
32
|
}
|
|
12
33
|
/**
|
|
13
34
|
* Клавиши сырого потока. Записаны кодами, а не знаками: управляющий знак посреди исходника
|
|
@@ -27,13 +48,23 @@ const DONE = ['\r', '\n'];
|
|
|
27
48
|
const CANCEL = [KEY_CANCEL, KEY_ESCAPE];
|
|
28
49
|
function moved(state, step) {
|
|
29
50
|
const count = state.choices.length;
|
|
30
|
-
|
|
51
|
+
if (!count) {
|
|
52
|
+
return state;
|
|
53
|
+
}
|
|
54
|
+
const cursor = (state.cursor + step + count) % count;
|
|
55
|
+
const chosen = state.single ? new Set([state.choices[cursor].id]) : state.chosen;
|
|
56
|
+
return { ...state, cursor, chosen };
|
|
31
57
|
}
|
|
32
58
|
function toggled(state) {
|
|
33
59
|
const choice = state.choices[state.cursor];
|
|
34
60
|
if (!choice) {
|
|
35
61
|
return state;
|
|
36
62
|
}
|
|
63
|
+
// При выборе одного пробел ставит отметку, но не снимает: отметить нечего — ответа не
|
|
64
|
+
// осталось бы вовсе, а брошенный выбор здесь говорится отдельной клавишей.
|
|
65
|
+
if (state.single) {
|
|
66
|
+
return { ...state, chosen: new Set([choice.id]) };
|
|
67
|
+
}
|
|
37
68
|
const chosen = new Set(state.chosen);
|
|
38
69
|
if (!chosen.delete(choice.id)) {
|
|
39
70
|
chosen.add(choice.id);
|
|
@@ -41,6 +72,9 @@ function toggled(state) {
|
|
|
41
72
|
return { ...state, chosen };
|
|
42
73
|
}
|
|
43
74
|
function switched(state) {
|
|
75
|
+
if (state.single) {
|
|
76
|
+
return state;
|
|
77
|
+
}
|
|
44
78
|
const all = state.chosen.size === state.choices.length;
|
|
45
79
|
return { ...state, chosen: all ? new Set() : new Set(state.choices.map((choice) => choice.id)) };
|
|
46
80
|
}
|
|
@@ -96,16 +130,20 @@ export function pressKey(state, key) {
|
|
|
96
130
|
/** Кусок потока целиком: разобрать на клавиши и провести состояние через каждую. */
|
|
97
131
|
export const pressChunk = (state, chunk) => keysOf(chunk).reduce(pressKey, state);
|
|
98
132
|
const widest = (choices) => choices.reduce((width, choice) => Math.max(width, choice.name.length), 0);
|
|
99
|
-
/** Разметка выбиралки строками. Курсор — стрелкой, выбранное — крестиком в скобках. */
|
|
133
|
+
/** Разметка выбиралки строками. Курсор — стрелкой, выбранное — крестиком или точкой в скобках. */
|
|
100
134
|
export function renderPicker(state, question) {
|
|
101
135
|
const width = widest(state.choices);
|
|
136
|
+
const help = state.single
|
|
137
|
+
? ' ↑↓ — двигаться, enter — готово'
|
|
138
|
+
: ' ↑↓ — двигаться, пробел — выбрать, a — все или ни одного, enter — готово';
|
|
102
139
|
return [
|
|
103
140
|
question,
|
|
104
|
-
|
|
141
|
+
help,
|
|
105
142
|
'',
|
|
106
143
|
...state.choices.map((choice, index) => {
|
|
107
|
-
const
|
|
108
|
-
|
|
144
|
+
const marked = state.chosen.has(choice.id);
|
|
145
|
+
const mark = state.single ? (marked ? '(•)' : '( )') : marked ? '[x]' : '[ ]';
|
|
146
|
+
return `${index === state.cursor ? '❯' : ' '} ${mark} ${choice.name.padEnd(width)} ${choice.title}`;
|
|
109
147
|
}),
|
|
110
148
|
];
|
|
111
149
|
}
|
package/lib/picker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"picker.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/picker.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAoBH,gGAAgG;AAChG,MAAM,UAAU,QAAQ,CAAC,OAA2B;IAChD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAe,EAAU,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;AACvI,CAAC;AAED;;;;;GAKG;AACH,MAAM,MAAM,GAAW,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;AAE/C,MAAM,CAAC,MAAM,MAAM,GAAW,GAAG,MAAM,IAAI,CAAC;AAC5C,MAAM,CAAC,MAAM,QAAQ,GAAW,GAAG,MAAM,IAAI,CAAC;AAC9C,MAAM,CAAC,MAAM,UAAU,GAAW,MAAM,CAAC;AACzC,MAAM,CAAC,MAAM,UAAU,GAAW,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;AAEzD,MAAM,EAAE,GAAsB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAC5C,MAAM,IAAI,GAAsB,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;AAChD,MAAM,IAAI,GAAsB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC7C,2EAA2E;AAC3E,MAAM,MAAM,GAAsB,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;AAE3D,SAAS,KAAK,CAAC,KAAmB,EAAE,IAAY;IAC5C,MAAM,KAAK,GAAW,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;IAE3C,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,GAAG,KAAK,CAAC,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;AACvF,CAAC;AAED,SAAS,OAAO,CAAC,KAAmB;IAChC,MAAM,MAAM,GAAwB,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAChE,IAAI,CAAC,MAAM,EAAE,CAAC;QACV,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,MAAM,MAAM,GAAgB,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAClD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;QAC5B,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC1B,CAAC;IAED,OAAO,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,CAAC;AAChC,CAAC;AAED,SAAS,QAAQ,CAAC,KAAmB;IACjC,MAAM,GAAG,GAAY,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;IAEhE,OAAO,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAe,EAAU,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;AACtH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,MAAM,CAAC,KAAa;IAChC,MAAM,MAAM,GAAsB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpD,MAAM,IAAI,GAAa,EAAE,CAAC;IAE1B,KAAK,IAAI,KAAK,GAAW,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACzD,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,MAAM,IAAI,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,GAAG,IAAI,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;YAC3F,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YACnD,KAAK,IAAI,CAAC,CAAC;QACf,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC7B,CAAC;IACL,CAAC;IAED,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,QAAQ,CAAC,KAAmB,EAAE,GAAW;IACrD,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QAChC,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,IAAI,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACnB,OAAO,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5B,CAAC;IACD,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC3B,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACzC,CAAC;IACD,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACpC,CAAC;IACD,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;QACd,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IACD,IAAI,GAAG,KAAK,GAAG,EAAE,CAAC;QACd,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;IAED,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,oFAAoF;AACpF,MAAM,CAAC,MAAM,UAAU,GAAyD,CAAC,KAAmB,EAAE,KAAa,EAAgB,EAAE,CACjI,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;AAE1C,MAAM,MAAM,GAA4C,CAAC,OAA2B,EAAU,EAAE,CAC5F,OAAO,CAAC,MAAM,CAAC,CAAC,KAAa,EAAE,MAAe,EAAU,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;AAEvG,uFAAuF;AACvF,MAAM,UAAU,YAAY,CAAC,KAAmB,EAAE,QAAgB;IAC9D,MAAM,KAAK,GAAW,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAE5C,OAAO;QACH,QAAQ;QACR,2EAA2E;QAC3E,EAAE;QACF,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAe,EAAE,KAAa,EAAU,EAAE;YAC5D,MAAM,IAAI,GAAW,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;YAE7D,OAAO,GAAG,KAAK,KAAK,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC;QAC3G,CAAC,CAAC;KACL,CAAC;AACN,CAAC","sourcesContent":["/**\n * Выбиралка галочками — её состояние и разметка, без единого касания терминала.\n *\n * Отделено от ввода-вывода по той же причине, что и команды: нажатие клавиши — это переход\n * состояния, и проверять его надо спекой, а не глазами на живом терминале. Сырой режим,\n * перерисовка и восстановление курсора живут на краю, в `bin/prompt.ts`, и не решают ничего.\n */\n\nexport interface IChoice {\n readonly id: string;\n /** Имя, которым ресурс называют в строке запуска: `access`. */\n readonly name: string;\n /** Заголовок ресурса — им человек и выбирает, имена ему ни о чём не говорят. */\n readonly title: string;\n}\n\nexport interface IPickerState {\n readonly choices: readonly IChoice[];\n readonly cursor: number;\n readonly chosen: ReadonlySet<string>;\n /** Выбор окончен, `chosen` — ответ. */\n readonly done: boolean;\n /** Выбор брошен: ответа нет, и подставлять вместо него умолчание нельзя. */\n readonly cancelled: boolean;\n}\n\n/** Первым предложением взято всё: отказ от закона — решение, и принимать его должен человек. */\nexport function pickerOf(choices: readonly IChoice[]): IPickerState {\n return { choices, cursor: 0, chosen: new Set(choices.map((choice: IChoice): string => choice.id)), done: false, cancelled: false };\n}\n\n/**\n * Клавиши сырого потока. Записаны кодами, а не знаками: управляющий знак посреди исходника\n * невидим, и правка рядом с ним ломает разбор молча.\n *\n * Стрелка приходит целиком, тремя знаками, — сравнивать надо всю последовательность.\n */\nconst ESCAPE: string = String.fromCharCode(27);\n\nexport const KEY_UP: string = `${ESCAPE}[A`;\nexport const KEY_DOWN: string = `${ESCAPE}[B`;\nexport const KEY_ESCAPE: string = ESCAPE;\nexport const KEY_CANCEL: string = String.fromCharCode(3);\n\nconst UP: readonly string[] = [KEY_UP, 'k'];\nconst DOWN: readonly string[] = [KEY_DOWN, 'j'];\nconst DONE: readonly string[] = ['\\r', '\\n'];\n/** Ctrl-C и escape. Оба — «я передумал», и оба обязаны уйти без записи. */\nconst CANCEL: readonly string[] = [KEY_CANCEL, KEY_ESCAPE];\n\nfunction moved(state: IPickerState, step: number): IPickerState {\n const count: number = state.choices.length;\n\n return count ? { ...state, cursor: (state.cursor + step + count) % count } : state;\n}\n\nfunction toggled(state: IPickerState): IPickerState {\n const choice: IChoice | undefined = state.choices[state.cursor];\n if (!choice) {\n return state;\n }\n const chosen: Set<string> = new Set(state.chosen);\n if (!chosen.delete(choice.id)) {\n chosen.add(choice.id);\n }\n\n return { ...state, chosen };\n}\n\nfunction switched(state: IPickerState): IPickerState {\n const all: boolean = state.chosen.size === state.choices.length;\n\n return { ...state, chosen: all ? new Set() : new Set(state.choices.map((choice: IChoice): string => choice.id)) };\n}\n\n/**\n * Кусок сырого потока, разобранный на клавиши.\n *\n * Терминал не обещает по куску на нажатие: набранное быстро, вставленное из буфера и всё, что\n * пришло по конвейеру, приезжает одной строкой. Неразобранный кусок читался бы как одна\n * незнакомая клавиша, и выбиралка молча не отзывалась бы ни на что — так она и повисла на\n * первом прогоне через конвейер.\n *\n * Escape-последовательность берётся целиком: `ESC [ A` — это стрелка, а не escape и две буквы.\n */\nexport function keysOf(chunk: string): readonly string[] {\n const points: readonly string[] = Array.from(chunk);\n const keys: string[] = [];\n\n for (let index: number = 0; index < points.length; index++) {\n if (points[index] === ESCAPE && points[index + 1] === '[' && points[index + 2] !== undefined) {\n keys.push(points.slice(index, index + 3).join(''));\n index += 2;\n } else {\n keys.push(points[index]);\n }\n }\n\n return keys;\n}\n\n/** Переход состояния по одной нажатой клавише. Незнакомая клавиша не делает ничего. */\nexport function pressKey(state: IPickerState, key: string): IPickerState {\n if (state.done || state.cancelled) {\n return state;\n }\n if (UP.includes(key)) {\n return moved(state, -1);\n }\n if (DOWN.includes(key)) {\n return moved(state, 1);\n }\n if (CANCEL.includes(key)) {\n return { ...state, cancelled: true };\n }\n if (DONE.includes(key)) {\n return { ...state, done: true };\n }\n if (key === ' ') {\n return toggled(state);\n }\n if (key === 'a') {\n return switched(state);\n }\n\n return state;\n}\n\n/** Кусок потока целиком: разобрать на клавиши и провести состояние через каждую. */\nexport const pressChunk: (state: IPickerState, chunk: string) => IPickerState = (state: IPickerState, chunk: string): IPickerState =>\n keysOf(chunk).reduce(pressKey, state);\n\nconst widest: (choices: readonly IChoice[]) => number = (choices: readonly IChoice[]): number =>\n choices.reduce((width: number, choice: IChoice): number => Math.max(width, choice.name.length), 0);\n\n/** Разметка выбиралки строками. Курсор — стрелкой, выбранное — крестиком в скобках. */\nexport function renderPicker(state: IPickerState, question: string): readonly string[] {\n const width: number = widest(state.choices);\n\n return [\n question,\n ' ↑↓ — двигаться, пробел — выбрать, a — все или ни одного, enter — готово',\n '',\n ...state.choices.map((choice: IChoice, index: number): string => {\n const mark: string = state.chosen.has(choice.id) ? 'x' : ' ';\n\n return `${index === state.cursor ? '❯' : ' '} [${mark}] ${choice.name.padEnd(width)} ${choice.title}`;\n }),\n ];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"picker.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/picker.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Выбиралка галочками — её состояние и разметка, без единого касания терминала.\n *\n * Отделено от ввода-вывода по той же причине, что и команды: нажатие клавиши — это переход\n * состояния, и проверять его надо спекой, а не глазами на живом терминале. Сырой режим,\n * перерисовка и восстановление курсора живут на краю, в `bin/prompt.ts`, и не решают ничего.\n */\n\nexport interface IChoice {\n readonly id: string;\n /** Имя, которым ресурс называют в строке запуска: `access`. */\n readonly name: string;\n /** Заголовок ресурса — им человек и выбирает, имена ему ни о чём не говорят. */\n readonly title: string;\n}\n\nexport interface IPickerState {\n readonly choices: readonly IChoice[];\n readonly cursor: number;\n readonly chosen: ReadonlySet<string>;\n /**\n * Выбирается ровно один. Вид ресурса галочками не набирается: репозиторий лежит либо в\n * одном месте, либо в другом, и «оба» здесь означало бы два правила поставки в одном дереве.\n */\n readonly single: boolean;\n /** Выбор окончен, `chosen` — ответ. */\n readonly done: boolean;\n /** Выбор брошен: ответа нет, и подставлять вместо него умолчание нельзя. */\n readonly cancelled: boolean;\n}\n\n/** Первым предложением взято всё: отказ от закона — решение, и принимать его должен человек. */\nexport function pickerOf(choices: readonly IChoice[]): IPickerState {\n return {\n choices,\n cursor: 0,\n chosen: new Set(choices.map((choice: IChoice): string => choice.id)),\n single: false,\n done: false,\n cancelled: false,\n };\n}\n\n/**\n * Выбор одного из. Отмечено то, что под курсором, и переезд курсора переносит отметку: так\n * ответ есть в любой момент, и enter отвечает без единого нажатия пробела.\n */\nexport function singlePickerOf(choices: readonly IChoice[]): IPickerState {\n return {\n choices,\n cursor: 0,\n chosen: new Set(choices.slice(0, 1).map((choice: IChoice): string => choice.id)),\n single: true,\n done: false,\n cancelled: false,\n };\n}\n\n/**\n * Клавиши сырого потока. Записаны кодами, а не знаками: управляющий знак посреди исходника\n * невидим, и правка рядом с ним ломает разбор молча.\n *\n * Стрелка приходит целиком, тремя знаками, — сравнивать надо всю последовательность.\n */\nconst ESCAPE: string = String.fromCharCode(27);\n\nexport const KEY_UP: string = `${ESCAPE}[A`;\nexport const KEY_DOWN: string = `${ESCAPE}[B`;\nexport const KEY_ESCAPE: string = ESCAPE;\nexport const KEY_CANCEL: string = String.fromCharCode(3);\n\nconst UP: readonly string[] = [KEY_UP, 'k'];\nconst DOWN: readonly string[] = [KEY_DOWN, 'j'];\nconst DONE: readonly string[] = ['\\r', '\\n'];\n/** Ctrl-C и escape. Оба — «я передумал», и оба обязаны уйти без записи. */\nconst CANCEL: readonly string[] = [KEY_CANCEL, KEY_ESCAPE];\n\nfunction moved(state: IPickerState, step: number): IPickerState {\n const count: number = state.choices.length;\n if (!count) {\n return state;\n }\n const cursor: number = (state.cursor + step + count) % count;\n const chosen: ReadonlySet<string> = state.single ? new Set([state.choices[cursor].id]) : state.chosen;\n\n return { ...state, cursor, chosen };\n}\n\nfunction toggled(state: IPickerState): IPickerState {\n const choice: IChoice | undefined = state.choices[state.cursor];\n if (!choice) {\n return state;\n }\n // При выборе одного пробел ставит отметку, но не снимает: отметить нечего — ответа не\n // осталось бы вовсе, а брошенный выбор здесь говорится отдельной клавишей.\n if (state.single) {\n return { ...state, chosen: new Set([choice.id]) };\n }\n const chosen: Set<string> = new Set(state.chosen);\n if (!chosen.delete(choice.id)) {\n chosen.add(choice.id);\n }\n\n return { ...state, chosen };\n}\n\nfunction switched(state: IPickerState): IPickerState {\n if (state.single) {\n return state;\n }\n const all: boolean = state.chosen.size === state.choices.length;\n\n return { ...state, chosen: all ? new Set() : new Set(state.choices.map((choice: IChoice): string => choice.id)) };\n}\n\n/**\n * Кусок сырого потока, разобранный на клавиши.\n *\n * Терминал не обещает по куску на нажатие: набранное быстро, вставленное из буфера и всё, что\n * пришло по конвейеру, приезжает одной строкой. Неразобранный кусок читался бы как одна\n * незнакомая клавиша, и выбиралка молча не отзывалась бы ни на что — так она и повисла на\n * первом прогоне через конвейер.\n *\n * Escape-последовательность берётся целиком: `ESC [ A` — это стрелка, а не escape и две буквы.\n */\nexport function keysOf(chunk: string): readonly string[] {\n const points: readonly string[] = Array.from(chunk);\n const keys: string[] = [];\n\n for (let index: number = 0; index < points.length; index++) {\n if (points[index] === ESCAPE && points[index + 1] === '[' && points[index + 2] !== undefined) {\n keys.push(points.slice(index, index + 3).join(''));\n index += 2;\n } else {\n keys.push(points[index]);\n }\n }\n\n return keys;\n}\n\n/** Переход состояния по одной нажатой клавише. Незнакомая клавиша не делает ничего. */\nexport function pressKey(state: IPickerState, key: string): IPickerState {\n if (state.done || state.cancelled) {\n return state;\n }\n if (UP.includes(key)) {\n return moved(state, -1);\n }\n if (DOWN.includes(key)) {\n return moved(state, 1);\n }\n if (CANCEL.includes(key)) {\n return { ...state, cancelled: true };\n }\n if (DONE.includes(key)) {\n return { ...state, done: true };\n }\n if (key === ' ') {\n return toggled(state);\n }\n if (key === 'a') {\n return switched(state);\n }\n\n return state;\n}\n\n/** Кусок потока целиком: разобрать на клавиши и провести состояние через каждую. */\nexport const pressChunk: (state: IPickerState, chunk: string) => IPickerState = (state: IPickerState, chunk: string): IPickerState =>\n keysOf(chunk).reduce(pressKey, state);\n\nconst widest: (choices: readonly IChoice[]) => number = (choices: readonly IChoice[]): number =>\n choices.reduce((width: number, choice: IChoice): number => Math.max(width, choice.name.length), 0);\n\n/** Разметка выбиралки строками. Курсор — стрелкой, выбранное — крестиком или точкой в скобках. */\nexport function renderPicker(state: IPickerState, question: string): readonly string[] {\n const width: number = widest(state.choices);\n const help: string = state.single\n ? ' ↑↓ — двигаться, enter — готово'\n : ' ↑↓ — двигаться, пробел — выбрать, a — все или ни одного, enter — готово';\n\n return [\n question,\n help,\n '',\n ...state.choices.map((choice: IChoice, index: number): string => {\n const marked: boolean = state.chosen.has(choice.id);\n const mark: string = state.single ? (marked ? '(•)' : '( )') : marked ? '[x]' : '[ ]';\n\n return `${index === state.cursor ? '❯' : ' '} ${mark} ${choice.name.padEnd(width)} ${choice.title}`;\n }),\n ];\n}\n"]}
|
package/lib/stamp.d.ts
CHANGED
|
@@ -12,11 +12,8 @@ export interface IStamped {
|
|
|
12
12
|
/** Двенадцати шестнадцатеричных знаков хватает: шапку читает человек, а не сверяет сеть. */
|
|
13
13
|
export declare const digestOf: (text: string) => string;
|
|
14
14
|
export declare function formatStamp(stamp: IStamp, path: string): string;
|
|
15
|
-
/**
|
|
16
|
-
* Шапка и тело разложенного файла. Ищется она в первых двух строках: у скрипта первой стоит
|
|
17
|
-
* `#!`, и требовать шапку строго первой значило бы ломать запуск.
|
|
18
|
-
*/
|
|
15
|
+
/** Шапка и тело разложенного файла. Ищется она в двух строках от начала тела. */
|
|
19
16
|
export declare function readStamped(text: string): IStamped | null;
|
|
20
|
-
/** Тело с шапкой:
|
|
17
|
+
/** Тело с шапкой: она встаёт после строки запуска и после вступления скила, иначе — первой. */
|
|
21
18
|
export declare function applyStamp(body: string, stamp: IStamp, path: string): string;
|
|
22
19
|
//# sourceMappingURL=stamp.d.ts.map
|
package/lib/stamp.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stamp.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/stamp.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,MAAM;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,QAAQ;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED,4FAA4F;AAC5F,eAAO,MAAM,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAC+B,CAAC;AAyBzE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAK/D;
|
|
1
|
+
{"version":3,"file":"stamp.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/stamp.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,MAAM;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,QAAQ;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED,4FAA4F;AAC5F,eAAO,MAAM,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAC+B,CAAC;AAyBzE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAK/D;AAsBD,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAczD;AAED,+FAA+F;AAC/F,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAM5E"}
|
package/lib/stamp.js
CHANGED
|
@@ -31,27 +31,42 @@ export function formatStamp(stamp, path) {
|
|
|
31
31
|
return `${comment.open}${text}${comment.close}`;
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
34
|
+
* С какой строки начинается тело, поверх которого встаёт шапка.
|
|
35
|
+
*
|
|
36
|
+
* Первой строкой шапка встать может не всегда. У скрипта первой идёт `#!` — иначе он перестаёт
|
|
37
|
+
* запускаться. У скила первым идёт вступление между `---`, и оно обязано начинаться со строки
|
|
38
|
+
* ноль: строка перед ним превращает вступление в обычный текст, скил теряет имя и описание и
|
|
39
|
+
* перестаёт находиться вовсе — молча, потому что файл при этом остаётся читаемым.
|
|
36
40
|
*/
|
|
41
|
+
function bodyStart(lines) {
|
|
42
|
+
if (lines[0]?.startsWith('#!')) {
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
if (lines[0]?.trim() !== '---') {
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
const closing = lines.findIndex((line, index) => index > 0 && line.trim() === '---');
|
|
49
|
+
return closing < 0 ? 0 : closing + 1;
|
|
50
|
+
}
|
|
51
|
+
/** Шапка и тело разложенного файла. Ищется она в двух строках от начала тела. */
|
|
37
52
|
export function readStamped(text) {
|
|
38
53
|
const lines = text.split('\n');
|
|
39
|
-
const
|
|
40
|
-
|
|
54
|
+
const from = bodyStart(lines);
|
|
55
|
+
const offset = lines.slice(from, from + 2).findIndex((line) => STAMP_LINE.test(line));
|
|
56
|
+
if (offset < 0) {
|
|
41
57
|
return null;
|
|
42
58
|
}
|
|
59
|
+
const index = from + offset;
|
|
43
60
|
const found = lines[index].match(STAMP_LINE);
|
|
44
61
|
const [, version, asset, hash] = found;
|
|
45
62
|
const body = [...lines.slice(0, index), ...lines.slice(index + 1)].join('\n');
|
|
46
63
|
return { stamp: { version, asset, hash }, body };
|
|
47
64
|
}
|
|
48
|
-
/** Тело с шапкой:
|
|
65
|
+
/** Тело с шапкой: она встаёт после строки запуска и после вступления скила, иначе — первой. */
|
|
49
66
|
export function applyStamp(body, stamp, path) {
|
|
50
67
|
const line = formatStamp(stamp, path);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
return `${line}\n${body}`;
|
|
68
|
+
const lines = body.split('\n');
|
|
69
|
+
const from = bodyStart(lines);
|
|
70
|
+
return [...lines.slice(0, from), line, ...lines.slice(from)].join('\n');
|
|
56
71
|
}
|
|
57
72
|
//# sourceMappingURL=stamp.js.map
|
package/lib/stamp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stamp.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/stamp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAezC,4FAA4F;AAC5F,MAAM,CAAC,MAAM,QAAQ,GAA6B,CAAC,IAAY,EAAU,EAAE,CACvE,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAYzE,MAAM,QAAQ,GAAkC,IAAI,GAAG,CAAmB;IACtE,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IACxC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IACjC,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IACnC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IAClC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;CACrC,CAAC,CAAC;AAEH,MAAM,SAAS,GAA+B,CAAC,IAAY,EAAY,EAAE,CACrE,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AAE3E,MAAM,UAAU,GAAW,kDAAkD,CAAC;AAE9E,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,IAAY;IACnD,MAAM,OAAO,GAAa,SAAS,CAAC,IAAI,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAW,WAAW,KAAK,CAAC,OAAO,MAAM,KAAK,CAAC,KAAK,MAAM,KAAK,CAAC,IAAI,mCAAmC,CAAC;IAElH,OAAO,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;AACpD,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"stamp.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/stamp.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Шапка разложенного файла: чем он положен, какой версией и то ли у него тело, которое пакет\n * клал.\n *\n * Разложенное коммитится — иначе его не видно ни в разборе ветки, ни на машине без установки\n * пакета. А раз коммитится, его правят руками: файл лежит в дереве проекта и выглядит обычным.\n * Правка при этом теряется на следующем `sync` молча, и «правило перестало действовать»\n * замечают уже по последствиям.\n *\n * Отсюда сумма тела в шапке. Она отвечает сразу на два вопроса, и оба нужны:\n *\n * - сумма не сходится с **фактическим** телом — файл правили руками;\n * - сумма не сходится со **свежесобранным** телом — разложенное отстало от пакета или от\n * надстройки, и его надо переложить.\n */\nimport { createHash } from 'node:crypto';\n\nexport interface IStamp {\n readonly version: string;\n /** Идентификатор ресурса в пакете, он же путь надстройки. */\n readonly asset: string;\n /** Сумма тела, каким его положил пакет. */\n readonly hash: string;\n}\n\nexport interface IStamped {\n readonly stamp: IStamp;\n readonly body: string;\n}\n\n/** Двенадцати шестнадцатеричных знаков хватает: шапку читает человек, а не сверяет сеть. */\nexport const digestOf: (text: string) => string = (text: string): string =>\n createHash('sha256').update(text, 'utf8').digest('hex').slice(0, 12);\n\n/**\n * Комментарий, невидимый при показе разметки, и обычный комментарий для всего остального.\n * Незнакомое расширение получает решётку: она комментарий в оболочке, в конфигах и в тексте, и\n * ошибиться ею дешевле, чем промолчать.\n */\ninterface IComment {\n readonly open: string;\n readonly close: string;\n}\n\nconst COMMENTS: ReadonlyMap<string, IComment> = new Map<string, IComment>([\n ['md', { open: '<!-- ', close: ' -->' }],\n ['sh', { open: '# ', close: '' }],\n ['mjs', { open: '// ', close: '' }],\n ['js', { open: '// ', close: '' }],\n ['ts', { open: '// ', close: '' }],\n]);\n\nconst commentOf: (path: string) => IComment = (path: string): IComment =>\n COMMENTS.get(path.split('.').pop() ?? '') ?? { open: '# ', close: '' };\n\nconst STAMP_LINE: RegExp = /rt-kit\\s+v(\\S+)\\s+·\\s+(\\S+)\\s+·\\s+([0-9a-f]{12})/;\n\nexport function formatStamp(stamp: IStamp, path: string): string {\n const comment: IComment = commentOf(path);\n const text: string = `rt-kit v${stamp.version} · ${stamp.asset} · ${stamp.hash} · правится надстройкой, не здесь`;\n\n return `${comment.open}${text}${comment.close}`;\n}\n\n/**\n * С какой строки начинается тело, поверх которого встаёт шапка.\n *\n * Первой строкой шапка встать может не всегда. У скрипта первой идёт `#!` — иначе он перестаёт\n * запускаться. У скила первым идёт вступление между `---`, и оно обязано начинаться со строки\n * ноль: строка перед ним превращает вступление в обычный текст, скил теряет имя и описание и\n * перестаёт находиться вовсе — молча, потому что файл при этом остаётся читаемым.\n */\nfunction bodyStart(lines: readonly string[]): number {\n if (lines[0]?.startsWith('#!')) {\n return 1;\n }\n if (lines[0]?.trim() !== '---') {\n return 0;\n }\n const closing: number = lines.findIndex((line: string, index: number): boolean => index > 0 && line.trim() === '---');\n\n return closing < 0 ? 0 : closing + 1;\n}\n\n/** Шапка и тело разложенного файла. Ищется она в двух строках от начала тела. */\nexport function readStamped(text: string): IStamped | null {\n const lines: string[] = text.split('\\n');\n const from: number = bodyStart(lines);\n const offset: number = lines.slice(from, from + 2).findIndex((line: string): boolean => STAMP_LINE.test(line));\n if (offset < 0) {\n return null;\n }\n\n const index: number = from + offset;\n const found: RegExpMatchArray = lines[index].match(STAMP_LINE) as RegExpMatchArray;\n const [, version, asset, hash]: string[] = found;\n const body: string = [...lines.slice(0, index), ...lines.slice(index + 1)].join('\\n');\n\n return { stamp: { version, asset, hash }, body };\n}\n\n/** Тело с шапкой: она встаёт после строки запуска и после вступления скила, иначе — первой. */\nexport function applyStamp(body: string, stamp: IStamp, path: string): string {\n const line: string = formatStamp(stamp, path);\n const lines: string[] = body.split('\\n');\n const from: number = bodyStart(lines);\n\n return [...lines.slice(0, from), line, ...lines.slice(from)].join('\\n');\n}\n"]}
|
package/lib/sync.d.ts
CHANGED
|
@@ -1,9 +1,38 @@
|
|
|
1
|
+
import { IGapOfVariant } from './catalog.js';
|
|
2
|
+
import { ICompanion } from './companion.js';
|
|
1
3
|
import { IConfig } from './config.js';
|
|
4
|
+
import { IHookBinding } from './hooks-map.js';
|
|
2
5
|
import { IPlanned } from './plan.js';
|
|
3
6
|
export interface ISyncResult {
|
|
4
7
|
readonly planned: readonly IPlanned[];
|
|
5
8
|
/** Дырки без значения, по ресурсам. Непустой список — отказ: подставлять нечего. */
|
|
6
9
|
readonly missing: ReadonlyMap<string, readonly string[]>;
|
|
10
|
+
/** Компаньоны разложенных правил: имена этого дерева, которые пишет проект. */
|
|
11
|
+
readonly companions: readonly ICompanion[];
|
|
12
|
+
/**
|
|
13
|
+
* Файлы, лежащие в дереве от ресурсов, от которых проект отказался.
|
|
14
|
+
*
|
|
15
|
+
* Отказ значит «больше не клади», а не «убери»: разложенное коммитится, рядом с правилом
|
|
16
|
+
* лежит написанный проектом компаньон, и стирать это молча пакет не вправе. Но и молчать
|
|
17
|
+
* нельзя — брошенный файл читается как действующее правило, и агент по нему работает.
|
|
18
|
+
*/
|
|
19
|
+
readonly abandoned: readonly string[];
|
|
20
|
+
/**
|
|
21
|
+
* Ресурсы, у которых виды есть, а вида под выбор дерева нет.
|
|
22
|
+
*
|
|
23
|
+
* Пропустить такой ресурс молча значит разложить правило и не разложить инструмент, который
|
|
24
|
+
* оно зовёт: дерево получает указание без исполнителя и узнаёт об этом, когда команда из
|
|
25
|
+
* правила не находится. Поэтому непустой список — отказ раскладки наравне с дыркой без
|
|
26
|
+
* значения и с файлом, который правили руками.
|
|
27
|
+
*/
|
|
28
|
+
readonly gaps: readonly IGapOfVariant[];
|
|
29
|
+
/**
|
|
30
|
+
* Разложенные гарды, которых нет в настройке агента: файл лежит, а позвать его некому.
|
|
31
|
+
*
|
|
32
|
+
* Раскладку это не отбивает — настройка принадлежит дереву, и пакет в неё не пишет, — но и
|
|
33
|
+
* молчать нельзя: гард, который не зовут, неотличим от гарда, который всё пропускает.
|
|
34
|
+
*/
|
|
35
|
+
readonly unbound: readonly IHookBinding[];
|
|
7
36
|
readonly written: readonly string[];
|
|
8
37
|
}
|
|
9
38
|
export declare function planSync(config: IConfig, root: string, version: string, assetsDir: string): ISyncResult;
|
package/lib/sync.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/sync.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,OAAO,EAAiB,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAkC,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/sync.ts"],"names":[],"mappings":"AAWA,OAAO,EAAmB,aAAa,EAA4B,MAAM,cAAc,CAAC;AACxF,OAAO,EAAE,UAAU,EAAyB,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,OAAO,EAAiB,MAAM,aAAa,CAAC;AACrD,OAAO,EAAa,YAAY,EAAgB,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAkC,MAAM,WAAW,CAAC;AAMrE,MAAM,WAAW,WAAW;IACxB,QAAQ,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,CAAC;IACtC,oFAAoF;IACpF,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;IACzD,+EAA+E;IAC/E,QAAQ,CAAC,UAAU,EAAE,SAAS,UAAU,EAAE,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC;;;;;;;OAOG;IACH,QAAQ,CAAC,IAAI,EAAE,SAAS,aAAa,EAAE,CAAC;IACxC;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IAC1C,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AA2DD,wBAAgB,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,CA6BvG;AAED,mGAAmG;AACnG,wBAAgB,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,CA0CtG;AAED,eAAO,MAAM,SAAS,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,SAAS,QAAQ,EACe,CAAC"}
|
package/lib/sync.js
CHANGED
|
@@ -5,13 +5,20 @@
|
|
|
5
5
|
* только потом писать. `sync --check` отличается от `sync` ровно последним шагом — иначе гейт
|
|
6
6
|
* пуша проверял бы не то, что кладёт раскладка.
|
|
7
7
|
*/
|
|
8
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
9
9
|
import { dirname, join } from 'node:path';
|
|
10
|
-
import { collectAssets } from './assets.js';
|
|
10
|
+
import { collectAssets, targetOf } from './assets.js';
|
|
11
|
+
import { readCatalog, variantGaps } from './catalog.js';
|
|
12
|
+
import { pathOf, planCompanion } from './companion.js';
|
|
11
13
|
import { OVERRIDES_DIR } from './config.js';
|
|
14
|
+
import { bindingOf, unboundHooks } from './hooks-map.js';
|
|
12
15
|
import { isPending, isRefusal, planFile } from './plan.js';
|
|
13
16
|
import { mergeDocuments, parseDocument, renderDocument } from './sections.js';
|
|
17
|
+
import { readStamped } from './stamp.js';
|
|
14
18
|
import { renderVars } from './vars.js';
|
|
19
|
+
import { matchesVariant } from './variants.js';
|
|
20
|
+
/** Шаблон черновика компаньона — тот же, что пакет кладёт проекту в шаблоны. */
|
|
21
|
+
const COMPANION_TEMPLATE = 'templates/implementation.md';
|
|
15
22
|
const read = (path) => (existsSync(path) ? readFileSync(path, 'utf8') : null);
|
|
16
23
|
/**
|
|
17
24
|
* Тело ресурса: текст пакета, поверх него надстройка проекта, и уже потом подстановка значений.
|
|
@@ -24,9 +31,45 @@ function renderAsset(asset, config, root) {
|
|
|
24
31
|
const merged = override ? renderDocument(mergeDocuments(parseDocument(asset.text), parseDocument(override))) : asset.text;
|
|
25
32
|
return renderVars(merged, config.vars);
|
|
26
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Что лежит в дереве от ресурсов, которые проект больше не берёт.
|
|
36
|
+
*
|
|
37
|
+
* Ищется по тем же правилам раскладки, что и всё остальное: путь ресурса вычисляется так, будто
|
|
38
|
+
* его кладут, и проверяется, лежит ли там файл с шапкой пакета. Чужой файл на том же пути
|
|
39
|
+
* брошенным не считается — его пакет не клал.
|
|
40
|
+
*/
|
|
41
|
+
function abandonedOf(config, root, assetsDir) {
|
|
42
|
+
const taken = new Set(collectAssets(config, assetsDir).map((asset) => asset.id));
|
|
43
|
+
return readCatalog(assetsDir)
|
|
44
|
+
.filter((entry) => !taken.has(entry.id) && matchesVariant(entry.variant, config.variants))
|
|
45
|
+
.map((entry) => targetOf(entry, config.layout))
|
|
46
|
+
.filter((path) => {
|
|
47
|
+
const existing = read(join(root, path));
|
|
48
|
+
return existing !== null && readStamped(existing) !== null;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Что о себе говорят гарды, которые дерево берёт. Читается у ресурсов, а не у разложенных
|
|
53
|
+
* файлов: карта нужна и до первой раскладки — чтобы было что вставить в настройку.
|
|
54
|
+
*/
|
|
55
|
+
function bindingsOf(config, assetsDir) {
|
|
56
|
+
const bindings = [];
|
|
57
|
+
for (const asset of collectAssets(config, assetsDir)) {
|
|
58
|
+
if (asset.kind !== 'hooks') {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const binding = bindingOf(asset.text, asset.target);
|
|
62
|
+
if (binding) {
|
|
63
|
+
bindings.push(binding);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return bindings;
|
|
67
|
+
}
|
|
27
68
|
export function planSync(config, root, version, assetsDir) {
|
|
28
69
|
const planned = [];
|
|
29
70
|
const missing = new Map();
|
|
71
|
+
const companions = [];
|
|
72
|
+
const template = read(join(assetsDir, COMPANION_TEMPLATE));
|
|
30
73
|
for (const asset of collectAssets(config, assetsDir)) {
|
|
31
74
|
const rendered = renderAsset(asset, config, root);
|
|
32
75
|
if (rendered.missing.length) {
|
|
@@ -34,15 +77,32 @@ export function planSync(config, root, version, assetsDir) {
|
|
|
34
77
|
continue;
|
|
35
78
|
}
|
|
36
79
|
planned.push(planFile({ path: asset.target, asset: asset.id, version, rendered: rendered.text, existing: read(join(root, asset.target)) }));
|
|
80
|
+
if (asset.kind === 'rules' && template !== null) {
|
|
81
|
+
companions.push(planCompanion(asset, read(join(root, pathOf(asset))), template));
|
|
82
|
+
}
|
|
37
83
|
}
|
|
38
|
-
return {
|
|
84
|
+
return {
|
|
85
|
+
planned,
|
|
86
|
+
missing,
|
|
87
|
+
companions,
|
|
88
|
+
abandoned: abandonedOf(config, root, assetsDir),
|
|
89
|
+
gaps: variantGaps(readCatalog(assetsDir), config),
|
|
90
|
+
unbound: unboundHooks(bindingsOf(config, assetsDir), root),
|
|
91
|
+
written: [],
|
|
92
|
+
};
|
|
39
93
|
}
|
|
40
94
|
/** Раскладка. Отказ хотя бы по одному файлу не пишет ничего: половина разложенного хуже целого. */
|
|
41
95
|
export function runSync(config, root, version, assetsDir) {
|
|
42
96
|
const result = planSync(config, root, version, assetsDir);
|
|
43
|
-
if (result.missing.size || result.planned.some((entry) => isRefusal(entry.outcome))) {
|
|
97
|
+
if (result.missing.size || result.gaps.length || result.planned.some((entry) => isRefusal(entry.outcome))) {
|
|
44
98
|
return result;
|
|
45
99
|
}
|
|
100
|
+
// Право на запуск переносится с файла в пакете. Без него гард отвечает отказом доступа, то
|
|
101
|
+
// есть ненулевым кодом, а ненулевой код у гарда значит «правка отбита»: разложенный набор
|
|
102
|
+
// отбивал бы подряд всё, включая сборку и тесты, и причина при этом нигде не называлась.
|
|
103
|
+
const executable = new Set(collectAssets(config, assetsDir)
|
|
104
|
+
.filter((asset) => asset.executable)
|
|
105
|
+
.map((asset) => asset.id));
|
|
46
106
|
const written = [];
|
|
47
107
|
for (const entry of result.planned) {
|
|
48
108
|
if (entry.content === null) {
|
|
@@ -51,8 +111,22 @@ export function runSync(config, root, version, assetsDir) {
|
|
|
51
111
|
const path = join(root, entry.path);
|
|
52
112
|
mkdirSync(dirname(path), { recursive: true });
|
|
53
113
|
writeFileSync(path, entry.content, 'utf8');
|
|
114
|
+
if (executable.has(entry.asset)) {
|
|
115
|
+
chmodSync(path, 0o755);
|
|
116
|
+
}
|
|
54
117
|
written.push(entry.path);
|
|
55
118
|
}
|
|
119
|
+
// Черновик компаньона кладётся только там, где файла нет вовсе. Он без шапки и без суммы:
|
|
120
|
+
// сверять в нём нечего — с первой правки проекта это его текст, а не текст пакета.
|
|
121
|
+
for (const companion of result.companions) {
|
|
122
|
+
if (companion.content === null) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const path = join(root, companion.path);
|
|
126
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
127
|
+
writeFileSync(path, companion.content, 'utf8');
|
|
128
|
+
written.push(companion.path);
|
|
129
|
+
}
|
|
56
130
|
return { ...result, written };
|
|
57
131
|
}
|
|
58
132
|
export const pendingOf = (result) => result.planned.filter((entry) => isPending(entry.outcome));
|
package/lib/sync.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/sync.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Раскладка ресурсов пакета в дерево проекта.\n *\n * Порядок один и тот же и для записи, и для проверки: собрать тело, решить судьбу файла, и\n * только потом писать. `sync --check` отличается от `sync` ровно последним шагом — иначе гейт\n * пуша проверял бы не то, что кладёт раскладка.\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\nimport { collectAssets, IAsset } from './assets.js';\nimport { IConfig, OVERRIDES_DIR } from './config.js';\nimport { IPlanned, isPending, isRefusal, planFile } from './plan.js';\nimport { mergeDocuments, parseDocument, renderDocument } from './sections.js';\nimport { IRenderResult, renderVars } from './vars.js';\n\nexport interface ISyncResult {\n readonly planned: readonly IPlanned[];\n /** Дырки без значения, по ресурсам. Непустой список — отказ: подставлять нечего. */\n readonly missing: ReadonlyMap<string, readonly string[]>;\n readonly written: readonly string[];\n}\n\nconst read: (path: string) => string | null = (path: string): string | null => (existsSync(path) ? readFileSync(path, 'utf8') : null);\n\n/**\n * Тело ресурса: текст пакета, поверх него надстройка проекта, и уже потом подстановка значений.\n *\n * Порядок именно такой. Надстройка тоже пишется с дырками — иначе проект, дописавший раздел про\n * свою главную ветку, зашил бы её имя в двух местах: в конфиге и в тексте.\n */\nfunction renderAsset(asset: IAsset, config: IConfig, root: string): IRenderResult {\n const override: string | null = read(join(root, OVERRIDES_DIR, asset.id));\n const merged: string = override ? renderDocument(mergeDocuments(parseDocument(asset.text), parseDocument(override))) : asset.text;\n\n return renderVars(merged, config.vars);\n}\n\nexport function planSync(config: IConfig, root: string, version: string, assetsDir: string): ISyncResult {\n const planned: IPlanned[] = [];\n const missing: Map<string, readonly string[]> = new Map();\n\n for (const asset of collectAssets(config, assetsDir)) {\n const rendered: IRenderResult = renderAsset(asset, config, root);\n if (rendered.missing.length) {\n missing.set(asset.id, rendered.missing);\n continue;\n }\n planned.push(\n planFile({ path: asset.target, asset: asset.id, version, rendered: rendered.text, existing: read(join(root, asset.target)) })\n );\n }\n\n return { planned, missing, written: [] };\n}\n\n/** Раскладка. Отказ хотя бы по одному файлу не пишет ничего: половина разложенного хуже целого. */\nexport function runSync(config: IConfig, root: string, version: string, assetsDir: string): ISyncResult {\n const result: ISyncResult = planSync(config, root, version, assetsDir);\n if (result.missing.size || result.planned.some((entry: IPlanned): boolean => isRefusal(entry.outcome))) {\n return result;\n }\n\n const written: string[] = [];\n for (const entry of result.planned) {\n if (entry.content === null) {\n continue;\n }\n const path: string = join(root, entry.path);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, entry.content, 'utf8');\n written.push(entry.path);\n }\n\n return { ...result, written };\n}\n\nexport const pendingOf: (result: ISyncResult) => readonly IPlanned[] = (result: ISyncResult): readonly IPlanned[] =>\n result.planned.filter((entry: IPlanned): boolean => isPending(entry.outcome));\n"]}
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/sync.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Раскладка ресурсов пакета в дерево проекта.\n *\n * Порядок один и тот же и для записи, и для проверки: собрать тело, решить судьбу файла, и\n * только потом писать. `sync --check` отличается от `sync` ровно последним шагом — иначе гейт\n * пуша проверял бы не то, что кладёт раскладка.\n */\nimport { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\nimport { collectAssets, IAsset, targetOf } from './assets.js';\nimport { IEntryOfCatalog, IGapOfVariant, readCatalog, variantGaps } from './catalog.js';\nimport { ICompanion, pathOf, planCompanion } from './companion.js';\nimport { IConfig, OVERRIDES_DIR } from './config.js';\nimport { bindingOf, IHookBinding, unboundHooks } from './hooks-map.js';\nimport { IPlanned, isPending, isRefusal, planFile } from './plan.js';\nimport { mergeDocuments, parseDocument, renderDocument } from './sections.js';\nimport { readStamped } from './stamp.js';\nimport { IRenderResult, renderVars } from './vars.js';\nimport { matchesVariant } from './variants.js';\n\nexport interface ISyncResult {\n readonly planned: readonly IPlanned[];\n /** Дырки без значения, по ресурсам. Непустой список — отказ: подставлять нечего. */\n readonly missing: ReadonlyMap<string, readonly string[]>;\n /** Компаньоны разложенных правил: имена этого дерева, которые пишет проект. */\n readonly companions: readonly ICompanion[];\n /**\n * Файлы, лежащие в дереве от ресурсов, от которых проект отказался.\n *\n * Отказ значит «больше не клади», а не «убери»: разложенное коммитится, рядом с правилом\n * лежит написанный проектом компаньон, и стирать это молча пакет не вправе. Но и молчать\n * нельзя — брошенный файл читается как действующее правило, и агент по нему работает.\n */\n readonly abandoned: readonly string[];\n /**\n * Ресурсы, у которых виды есть, а вида под выбор дерева нет.\n *\n * Пропустить такой ресурс молча значит разложить правило и не разложить инструмент, который\n * оно зовёт: дерево получает указание без исполнителя и узнаёт об этом, когда команда из\n * правила не находится. Поэтому непустой список — отказ раскладки наравне с дыркой без\n * значения и с файлом, который правили руками.\n */\n readonly gaps: readonly IGapOfVariant[];\n /**\n * Разложенные гарды, которых нет в настройке агента: файл лежит, а позвать его некому.\n *\n * Раскладку это не отбивает — настройка принадлежит дереву, и пакет в неё не пишет, — но и\n * молчать нельзя: гард, который не зовут, неотличим от гарда, который всё пропускает.\n */\n readonly unbound: readonly IHookBinding[];\n readonly written: readonly string[];\n}\n\n/** Шаблон черновика компаньона — тот же, что пакет кладёт проекту в шаблоны. */\nconst COMPANION_TEMPLATE: string = 'templates/implementation.md';\n\nconst read: (path: string) => string | null = (path: string): string | null => (existsSync(path) ? readFileSync(path, 'utf8') : null);\n\n/**\n * Тело ресурса: текст пакета, поверх него надстройка проекта, и уже потом подстановка значений.\n *\n * Порядок именно такой. Надстройка тоже пишется с дырками — иначе проект, дописавший раздел про\n * свою главную ветку, зашил бы её имя в двух местах: в конфиге и в тексте.\n */\nfunction renderAsset(asset: IAsset, config: IConfig, root: string): IRenderResult {\n const override: string | null = read(join(root, OVERRIDES_DIR, asset.id));\n const merged: string = override ? renderDocument(mergeDocuments(parseDocument(asset.text), parseDocument(override))) : asset.text;\n\n return renderVars(merged, config.vars);\n}\n\n/**\n * Что лежит в дереве от ресурсов, которые проект больше не берёт.\n *\n * Ищется по тем же правилам раскладки, что и всё остальное: путь ресурса вычисляется так, будто\n * его кладут, и проверяется, лежит ли там файл с шапкой пакета. Чужой файл на том же пути\n * брошенным не считается — его пакет не клал.\n */\nfunction abandonedOf(config: IConfig, root: string, assetsDir: string): readonly string[] {\n const taken: ReadonlySet<string> = new Set(collectAssets(config, assetsDir).map((asset: IAsset): string => asset.id));\n\n return readCatalog(assetsDir)\n .filter((entry: IEntryOfCatalog): boolean => !taken.has(entry.id) && matchesVariant(entry.variant, config.variants))\n .map((entry: IEntryOfCatalog): string => targetOf(entry, config.layout))\n .filter((path: string): boolean => {\n const existing: string | null = read(join(root, path));\n\n return existing !== null && readStamped(existing) !== null;\n });\n}\n\n/**\n * Что о себе говорят гарды, которые дерево берёт. Читается у ресурсов, а не у разложенных\n * файлов: карта нужна и до первой раскладки — чтобы было что вставить в настройку.\n */\nfunction bindingsOf(config: IConfig, assetsDir: string): readonly IHookBinding[] {\n const bindings: IHookBinding[] = [];\n for (const asset of collectAssets(config, assetsDir)) {\n if (asset.kind !== 'hooks') {\n continue;\n }\n const binding: IHookBinding | null = bindingOf(asset.text, asset.target);\n if (binding) {\n bindings.push(binding);\n }\n }\n\n return bindings;\n}\n\nexport function planSync(config: IConfig, root: string, version: string, assetsDir: string): ISyncResult {\n const planned: IPlanned[] = [];\n const missing: Map<string, readonly string[]> = new Map();\n const companions: ICompanion[] = [];\n const template: string | null = read(join(assetsDir, COMPANION_TEMPLATE));\n\n for (const asset of collectAssets(config, assetsDir)) {\n const rendered: IRenderResult = renderAsset(asset, config, root);\n if (rendered.missing.length) {\n missing.set(asset.id, rendered.missing);\n continue;\n }\n planned.push(\n planFile({ path: asset.target, asset: asset.id, version, rendered: rendered.text, existing: read(join(root, asset.target)) })\n );\n if (asset.kind === 'rules' && template !== null) {\n companions.push(planCompanion(asset, read(join(root, pathOf(asset))), template));\n }\n }\n\n return {\n planned,\n missing,\n companions,\n abandoned: abandonedOf(config, root, assetsDir),\n gaps: variantGaps(readCatalog(assetsDir), config),\n unbound: unboundHooks(bindingsOf(config, assetsDir), root),\n written: [],\n };\n}\n\n/** Раскладка. Отказ хотя бы по одному файлу не пишет ничего: половина разложенного хуже целого. */\nexport function runSync(config: IConfig, root: string, version: string, assetsDir: string): ISyncResult {\n const result: ISyncResult = planSync(config, root, version, assetsDir);\n if (result.missing.size || result.gaps.length || result.planned.some((entry: IPlanned): boolean => isRefusal(entry.outcome))) {\n return result;\n }\n\n // Право на запуск переносится с файла в пакете. Без него гард отвечает отказом доступа, то\n // есть ненулевым кодом, а ненулевой код у гарда значит «правка отбита»: разложенный набор\n // отбивал бы подряд всё, включая сборку и тесты, и причина при этом нигде не называлась.\n const executable: ReadonlySet<string> = new Set(\n collectAssets(config, assetsDir)\n .filter((asset: IAsset): boolean => asset.executable)\n .map((asset: IAsset): string => asset.id)\n );\n\n const written: string[] = [];\n for (const entry of result.planned) {\n if (entry.content === null) {\n continue;\n }\n const path: string = join(root, entry.path);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, entry.content, 'utf8');\n if (executable.has(entry.asset)) {\n chmodSync(path, 0o755);\n }\n written.push(entry.path);\n }\n\n // Черновик компаньона кладётся только там, где файла нет вовсе. Он без шапки и без суммы:\n // сверять в нём нечего — с первой правки проекта это его текст, а не текст пакета.\n for (const companion of result.companions) {\n if (companion.content === null) {\n continue;\n }\n const path: string = join(root, companion.path);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, companion.content, 'utf8');\n written.push(companion.path);\n }\n\n return { ...result, written };\n}\n\nexport const pendingOf: (result: ISyncResult) => readonly IPlanned[] = (result: ISyncResult): readonly IPlanned[] =>\n result.planned.filter((entry: IPlanned): boolean => isPending(entry.outcome));\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface IOptionOfAxis {
|
|
2
|
+
/** Значение, которое проект пишет в конфиг и которое стоит в имени файла: `github`. */
|
|
3
|
+
readonly value: string;
|
|
4
|
+
/** Чем этот вид отличается от соседнего — им человек и выбирает. */
|
|
5
|
+
readonly title: string;
|
|
6
|
+
}
|
|
7
|
+
export interface IAxis {
|
|
8
|
+
/** Имя оси, оно же ключ в `variants` конфига: `host`. */
|
|
9
|
+
readonly name: string;
|
|
10
|
+
/** Вопрос, которым ось спрашивают при заведении конфига. */
|
|
11
|
+
readonly question: string;
|
|
12
|
+
/** Чем ось названа в перечнях и отказах. */
|
|
13
|
+
readonly title: string;
|
|
14
|
+
readonly options: readonly IOptionOfAxis[];
|
|
15
|
+
}
|
|
16
|
+
/** Ось и вид, которым помечен ресурс. У ресурса без пометки его нет. */
|
|
17
|
+
export interface IVariant {
|
|
18
|
+
readonly axis: string;
|
|
19
|
+
readonly value: string;
|
|
20
|
+
}
|
|
21
|
+
export declare const VARIANTS_FILE: string;
|
|
22
|
+
/** Оси, объявленные пакетом. Нет файла — осей нет, и все ресурсы считаются общими. */
|
|
23
|
+
export declare function readAxes(assetsDir: string): readonly IAxis[];
|
|
24
|
+
/**
|
|
25
|
+
* Чем помечено имя файла: `git-workflow.github.md` — видом `github` оси `host`.
|
|
26
|
+
*
|
|
27
|
+
* Вид ищется среди объявленных значений, а не по одному лишь виду имени. Иначе `app.config.md`
|
|
28
|
+
* прочитался бы как ресурс `app` вида `config`, и раскладка потеряла бы файл молча — по
|
|
29
|
+
* причине, которую в имени не разглядеть.
|
|
30
|
+
*/
|
|
31
|
+
export declare function variantOf(file: string, axes: readonly IAxis[]): IVariant | null;
|
|
32
|
+
/** Имя файла без пометки вида: под ним ресурс и ложится в дерево проекта. */
|
|
33
|
+
export declare function withoutVariant(file: string, variant: IVariant | null): string;
|
|
34
|
+
/**
|
|
35
|
+
* Взят ли ресурс при этом выборе видов.
|
|
36
|
+
*
|
|
37
|
+
* Ресурс без пометки берётся всегда. Помеченный — только когда проект назвал ровно этот вид:
|
|
38
|
+
* невыбранная ось значит, что дерево о ней ничего не сказало, и класть ему наугад один из трёх
|
|
39
|
+
* git-flow хуже, чем не класть ни одного.
|
|
40
|
+
*/
|
|
41
|
+
export declare function matchesVariant(variant: IVariant | null, chosen: Readonly<Record<string, string>>): boolean;
|
|
42
|
+
/** Оси, о которых проект молчит, хотя пакет их объявил, — по ним раскладка не полна. */
|
|
43
|
+
export declare function unansweredAxes(axes: readonly IAxis[], chosen: Readonly<Record<string, string>>): readonly IAxis[];
|
|
44
|
+
//# sourceMappingURL=variants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"variants.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/variants.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,aAAa;IAC1B,uFAAuF;IACvF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,KAAK;IAClB,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,4CAA4C;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;CAC9C;AAED,wEAAwE;AACxE,MAAM,WAAW,QAAQ;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,aAAa,EAAE,MAAwB,CAAC;AAErD,sFAAsF;AACtF,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,KAAK,EAAE,CAiB5D;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,KAAK,EAAE,GAAG,QAAQ,GAAG,IAAI,CAc/E;AAED,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,GAAG,MAAM,CAQ7E;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,QAAQ,GAAG,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,OAAO,CAE1G;AAED,wFAAwF;AACxF,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,KAAK,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,KAAK,EAAE,CAIjH"}
|