@rsconcept/rstool 1.9.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.
Files changed (105) hide show
  1. package/README.md +3 -3
  2. package/dist/mappers/model-adapter.d.ts +1 -2
  3. package/dist/mappers/schema-adapter.d.ts +1 -2
  4. package/dist/mappers/types.d.ts +4 -5
  5. package/dist/models/rstool-agent.js +1 -2
  6. package/dist/session/session-store.d.ts +3 -3
  7. package/dist/session/session-store.js +1 -2
  8. package/dist/wrapper/client.d.ts +3 -4
  9. package/docs/BRIEF.md +93 -0
  10. package/docs/CONCEPTUAL-SCHEMA.md +16 -1
  11. package/docs/CONSTRUCTIONS.md +14 -2
  12. package/docs/DIAGNOSTICS.md +3 -2
  13. package/docs/DOMAIN.md +12 -0
  14. package/docs/METHOD.md +208 -0
  15. package/docs/PORTAL-API.md +38 -285
  16. package/docs/PORTAL-MODELS.md +24 -0
  17. package/docs/PORTAL-OSS.md +56 -0
  18. package/docs/PORTAL-PUBLISH.md +39 -0
  19. package/docs/PORTAL-READ.md +301 -0
  20. package/docs/README.md +7 -4
  21. package/docs/REQUIREMENTS.md +61 -0
  22. package/docs/SYNTAX.md +4 -3
  23. package/docs/TYPIFICATION.md +1 -1
  24. package/docs/brief.schema.json +173 -0
  25. package/examples/README.md +16 -2
  26. package/examples/access-control/NOTES.md +23 -0
  27. package/examples/access-control/anti/collapsed.json +439 -0
  28. package/examples/access-control/anti/weakened.json +439 -0
  29. package/examples/access-control/brief-v1.json +231 -0
  30. package/examples/access-control/brief.json +310 -0
  31. package/examples/access-control/build-rsform.ts +151 -0
  32. package/examples/access-control/requirements-v1.md +9 -0
  33. package/examples/access-control/requirements-v2.md +4 -0
  34. package/examples/access-control/rsform-session-v1.json +439 -0
  35. package/examples/access-control/rsform-session.json +523 -0
  36. package/examples/audit-diagnostics.ts +9 -1
  37. package/examples/briefs.test.ts +126 -0
  38. package/examples/clinic-booking/NOTES.md +24 -0
  39. package/examples/clinic-booking/anti/collapsed.json +615 -0
  40. package/examples/clinic-booking/anti/species-as-genus.json +568 -0
  41. package/examples/clinic-booking/brief-v1.json +332 -0
  42. package/examples/clinic-booking/brief.json +405 -0
  43. package/examples/clinic-booking/build-rsform.ts +174 -0
  44. package/examples/clinic-booking/requirements-v1.md +9 -0
  45. package/examples/clinic-booking/requirements-v2.md +4 -0
  46. package/examples/clinic-booking/rsform-session-v1.json +615 -0
  47. package/examples/clinic-booking/rsform-session.json +700 -0
  48. package/examples/diagnostics-utils.ts +26 -0
  49. package/examples/kinship/brief.json +337 -0
  50. package/examples/kinship/build-rsform.ts +7 -4
  51. package/examples/kinship/rsform-session.json +20 -63
  52. package/examples/kinship/rsmodel-session.json +14 -14
  53. package/examples/kinship-subordination/NOTES.md +49 -0
  54. package/examples/kinship-subordination/brief.json +161 -0
  55. package/examples/kinship-subordination/build-rsform.ts +100 -0
  56. package/examples/kinship-subordination/rsform-session.json +795 -0
  57. package/examples/library-loans/NOTES.md +46 -0
  58. package/examples/library-loans/anti/collapsed.json +481 -0
  59. package/examples/library-loans/anti/species-as-genus.json +334 -0
  60. package/examples/library-loans/anti/uncovered.json +406 -0
  61. package/examples/library-loans/anti/weakened.json +464 -0
  62. package/examples/library-loans/brief-v1.json +340 -0
  63. package/examples/library-loans/brief.json +502 -0
  64. package/examples/library-loans/build-rsform.ts +239 -0
  65. package/examples/library-loans/requirements-v1.md +11 -0
  66. package/examples/library-loans/requirements-v2.md +5 -0
  67. package/examples/library-loans/rsform-session-v1.json +481 -0
  68. package/examples/library-loans/rsform-session.json +652 -0
  69. package/examples/library-loans/source-markup.md +18 -0
  70. package/examples/movd/brief.json +220 -0
  71. package/examples/movd/build-rsform.ts +5 -2
  72. package/examples/movd/rsform-session.json +14 -46
  73. package/examples/subordination/NOTES.md +59 -0
  74. package/examples/subordination/anti/collapsed.json +556 -0
  75. package/examples/subordination/anti/single-relation.json +125 -0
  76. package/examples/subordination/anti/transitive.json +573 -0
  77. package/examples/subordination/anti/weakened.json +539 -0
  78. package/examples/subordination/brief.json +386 -0
  79. package/examples/subordination/build-rsform.ts +63 -0
  80. package/examples/subordination/constants.ts +115 -0
  81. package/examples/subordination/rsform-session.json +556 -0
  82. package/examples/subordination/source-markup.md +19 -0
  83. package/examples/subordination/source.md +40 -0
  84. package/package.json +18 -10
  85. package/skills/INSTALL.md +28 -29
  86. package/skills/README.md +25 -10
  87. package/skills/rs-core/SKILL.md +56 -0
  88. package/skills/rs-explain/SKILL.md +28 -0
  89. package/skills/rs-explicate/SKILL.md +71 -0
  90. package/skills/rs-interpret/SKILL.md +67 -0
  91. package/skills/rs-portal/SKILL.md +51 -0
  92. package/skills/rs-review/SKILL.md +79 -0
  93. package/skills/rs-scope/SKILL.md +67 -0
  94. package/skills/rs-synthesis/SKILL.md +52 -0
  95. package/skills/rstool/SKILL.md +70 -0
  96. package/skills/rstool-helper/GUIDE.md +18 -299
  97. package/skills/rstool-helper/SKILL.md +8 -58
  98. package/skills/{rstool-helper → rstool-runtime}/EXAMPLES.md +16 -12
  99. package/skills/{rstool-helper → rstool-runtime}/REFERENCE.md +7 -5
  100. package/skills/rstool-runtime/SKILL.md +96 -0
  101. package/skills/scripts/brief-check.test.ts +165 -0
  102. package/skills/scripts/brief-check.ts +826 -0
  103. package/skills/scripts/check-brief.ts +94 -0
  104. package/skills/scripts/rstool-skills.mjs +119 -0
  105. package/skills/scripts/skill-stubs.test.ts +48 -0
@@ -0,0 +1,94 @@
1
+ /**
2
+ * CLI: check a session against its brief and print readiness.
3
+ *
4
+ * Usage (from the rstool package root):
5
+ * tsx skills/scripts/check-brief.ts --session <session.json> --brief <brief.json> [--base <v1-session.json>] [--review] [--json]
6
+ *
7
+ * Exit code: 0 — no blockers, 1 — blockers, 2 — usage or input error.
8
+ */
9
+ import { readFileSync } from 'node:fs';
10
+ import { resolve } from 'node:path';
11
+
12
+ import { type Brief, checkBrief, type BriefCheckResult } from './brief-check';
13
+
14
+ function parseArgs(argv: string[]): Record<string, string | true> {
15
+ const args: Record<string, string | true> = {};
16
+ for (let i = 0; i < argv.length; i++) {
17
+ const key = argv[i];
18
+ if (!key.startsWith('--')) {
19
+ continue;
20
+ }
21
+ const next = argv[i + 1];
22
+ if (next === undefined || next.startsWith('--')) {
23
+ args[key.slice(2)] = true;
24
+ } else {
25
+ args[key.slice(2)] = next;
26
+ i++;
27
+ }
28
+ }
29
+ return args;
30
+ }
31
+
32
+ function readText(path: string): string {
33
+ return readFileSync(resolve(path), 'utf8').replace(/^/, '');
34
+ }
35
+
36
+ function printReport({ readiness, scenarios }: BriefCheckResult) {
37
+ const lines: string[] = [];
38
+ lines.push(`Этап: ${readiness.stage}${readiness.ready ? ' — блокеров нет' : ''}`);
39
+ lines.push(`Диагностики: ошибок ${readiness.diagnostics.errors}, предупреждений ${readiness.diagnostics.warnings}`);
40
+ lines.push(
41
+ `Покрытие: требования ${readiness.coverage.expressiveness}, инварианты ${readiness.coverage.invariants}, различения ${readiness.coverage.distinctions}`
42
+ );
43
+ lines.push(
44
+ `Сценарии: прошло ${readiness.scenarios.passed}, провалено ${readiness.scenarios.failed}, не хватает ${readiness.scenarios.missing}`
45
+ );
46
+ if (scenarios.length > 0) {
47
+ lines.push('', 'Матрица приёмки:');
48
+ for (const scenario of scenarios) {
49
+ lines.push(
50
+ ` ${scenario.passed ? '✓' : '✗'} ${scenario.id} ${scenario.title}${scenario.error ? ` — ${scenario.error}` : ''}`
51
+ );
52
+ for (const check of scenario.checks) {
53
+ lines.push(
54
+ ` ${check.passed ? '✓' : '✗'} ${check.target}: ожидалось ${check.expected}, получено ${check.actual}`
55
+ );
56
+ }
57
+ }
58
+ }
59
+ if (readiness.blockers.length > 0) {
60
+ lines.push('', 'Блокеры:', ...readiness.blockers.map(item => ` - ${item}`));
61
+ }
62
+ if (readiness.warnings.length > 0) {
63
+ lines.push('', 'Предупреждения:', ...readiness.warnings.map(item => ` - ${item}`));
64
+ }
65
+ lines.push('', `Дальше: ${readiness.next}`);
66
+ console.log(lines.join('\n'));
67
+ }
68
+
69
+ async function main() {
70
+ const args = parseArgs(process.argv.slice(2));
71
+ if (typeof args.session !== 'string' || typeof args.brief !== 'string') {
72
+ console.error(
73
+ 'Usage: tsx skills/scripts/check-brief.ts --session <session.json> --brief <brief.json> [--base <session.json>] [--review] [--json]'
74
+ );
75
+ process.exit(2);
76
+ }
77
+ const result = await checkBrief({
78
+ session: readText(args.session),
79
+ brief: JSON.parse(readText(args.brief)) as Brief,
80
+ base: typeof args.base === 'string' ? readText(args.base) : undefined,
81
+ mode: args.review ? 'review' : 'build'
82
+ });
83
+ if (args.json) {
84
+ console.log(JSON.stringify(result, null, 2));
85
+ } else {
86
+ printReport(result);
87
+ }
88
+ process.exit(result.readiness.ready ? 0 : 1);
89
+ }
90
+
91
+ main().catch((error: unknown) => {
92
+ console.error(error instanceof Error ? error.message : error);
93
+ process.exit(2);
94
+ });
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Writes the `rstool` entry skill stub into a project skills directory.
4
+ *
5
+ * The stub holds only `name`, `description` and the path to the real router skill,
6
+ * so nothing has to be re-synced after a package update. Which directory a host loads
7
+ * is the agent's call (skills/INSTALL.md), so there is no default `--dir`.
8
+ *
9
+ * Usage:
10
+ * rstool-skills install --dir <skills-dir>... [--source <router SKILL.md>] [--target <path>]
11
+ * rstool-skills stub [--source <router SKILL.md>] [--target <path>] (prints the stub)
12
+ *
13
+ * Defaults: --source — router skill of this package; --target — its path from the
14
+ * current directory, or node_modules/@rsconcept/rstool/skills/rstool/SKILL.md when
15
+ * the package lies outside it.
16
+ */
17
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
18
+ import { dirname, join, relative, resolve } from 'node:path';
19
+ import { fileURLToPath } from 'node:url';
20
+
21
+ const ROUTER = fileURLToPath(new URL('../rstool/SKILL.md', import.meta.url));
22
+
23
+ /** Extracts the YAML front matter `description` block (folded `>-` or single line) verbatim. */
24
+ export function readDescription(skillText) {
25
+ const match = /^---\n([\s\S]*?)\n---/.exec(skillText.replace(/\r\n/g, '\n'));
26
+ if (!match) {
27
+ throw new Error('SKILL.md has no front matter');
28
+ }
29
+ const lines = match[1].split('\n');
30
+ const start = lines.findIndex(line => line.startsWith('description:'));
31
+ if (start < 0) {
32
+ throw new Error('SKILL.md has no description');
33
+ }
34
+ const block = [lines[start]];
35
+ for (const line of lines.slice(start + 1)) {
36
+ if (!line.startsWith(' ')) {
37
+ break;
38
+ }
39
+ block.push(line);
40
+ }
41
+ return block.join('\n');
42
+ }
43
+
44
+ /** Renders the stub skill pointing at `targetPath` (written as is, use forward slashes). */
45
+ export function renderStub(skillText, targetPath) {
46
+ return [
47
+ '---',
48
+ 'name: rstool',
49
+ readDescription(skillText),
50
+ '---',
51
+ '',
52
+ '# rstool',
53
+ '',
54
+ `Заглушка для автосрабатывания. Прочитай \`${targetPath}\` и следуй ему.`,
55
+ '',
56
+ 'Содержимое скилла — только там; эта заглушка его не повторяет и обновлять её после обновления пакета не нужно.',
57
+ ''
58
+ ].join('\n');
59
+ }
60
+
61
+ function parseArgs(argv) {
62
+ const args = { dirs: [], source: undefined, target: undefined };
63
+ for (let i = 0; i < argv.length; i++) {
64
+ const value = argv[i + 1];
65
+ if (argv[i] === '--dir') {
66
+ args.dirs.push(value);
67
+ i++;
68
+ } else if (argv[i] === '--source') {
69
+ args.source = value;
70
+ i++;
71
+ } else if (argv[i] === '--target') {
72
+ args.target = value;
73
+ i++;
74
+ }
75
+ }
76
+ return args;
77
+ }
78
+
79
+ function stubTarget(args, source) {
80
+ const fromProject = relative(process.cwd(), source).split('\\').join('/');
81
+ return (
82
+ args.target ??
83
+ (fromProject.startsWith('..') ? 'node_modules/@rsconcept/rstool/skills/rstool/SKILL.md' : fromProject)
84
+ );
85
+ }
86
+
87
+ function install(args) {
88
+ const source = resolve(args.source ?? ROUTER);
89
+ const text = readFileSync(source, 'utf8');
90
+ const target = stubTarget(args, source);
91
+ for (const dir of args.dirs) {
92
+ const path = join(dir, 'rstool', 'SKILL.md');
93
+ mkdirSync(dirname(path), { recursive: true });
94
+ writeFileSync(path, renderStub(text, target), 'utf8');
95
+ console.log(`wrote ${path} → ${target}`);
96
+ const legacy = join(dir, 'rstool-helper', 'SKILL.md');
97
+ if (existsSync(legacy)) {
98
+ console.log(`note: ${legacy} is the old copied skill; remove it — rstool replaces it`);
99
+ }
100
+ }
101
+ }
102
+
103
+ const isMain = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
104
+ if (isMain) {
105
+ const [command, ...rest] = process.argv.slice(2);
106
+ const args = parseArgs(rest);
107
+ if (command === 'install' && args.dirs.length > 0) {
108
+ install(args);
109
+ } else if (command === 'stub') {
110
+ const source = resolve(args.source ?? ROUTER);
111
+ process.stdout.write(renderStub(readFileSync(source, 'utf8'), stubTarget(args, source)));
112
+ } else {
113
+ console.error(
114
+ 'Usage: rstool-skills install --dir <skills-dir>... | rstool-skills stub [--source <SKILL.md>] [--target <path>]\n' +
115
+ 'Which skills directory to use: node_modules/@rsconcept/rstool/skills/INSTALL.md'
116
+ );
117
+ process.exit(2);
118
+ }
119
+ }
@@ -0,0 +1,48 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join, resolve } from 'node:path';
5
+
6
+ import { describe, expect, it } from 'vitest';
7
+
8
+ const SCRIPT = resolve(import.meta.dirname, 'rstool-skills.mjs');
9
+ const ROUTER = resolve(import.meta.dirname, '../rstool/SKILL.md');
10
+ const WORKSPACE = resolve(import.meta.dirname, '../../../..');
11
+ const STUBS = ['.agents/skills/rstool/SKILL.md', '.claude/skills/rstool/SKILL.md'];
12
+
13
+ function renderStub(target: string): string {
14
+ return execFileSync('node', [SCRIPT, 'stub', '--source', ROUTER, '--target', target], { encoding: 'utf8' });
15
+ }
16
+
17
+ describe('rstool skill stubs', () => {
18
+ it.each(STUBS)('workspace %s matches the router skill', stub => {
19
+ const path = join(WORKSPACE, stub);
20
+ if (!existsSync(path)) {
21
+ return;
22
+ }
23
+ expect(readFileSync(path, 'utf8')).toBe(renderStub('rsconcept/rstool/skills/rstool/SKILL.md'));
24
+ });
25
+
26
+ it('install requires an explicit skills directory', () => {
27
+ const project = mkdtempSync(join(tmpdir(), 'rstool-skills-'));
28
+ try {
29
+ expect(() => execFileSync('node', [SCRIPT, 'install'], { cwd: project, stdio: 'pipe' })).toThrow();
30
+ expect(existsSync(join(project, '.claude'))).toBe(false);
31
+ expect(existsSync(join(project, '.agents'))).toBe(false);
32
+ } finally {
33
+ rmSync(project, { recursive: true, force: true });
34
+ }
35
+ });
36
+
37
+ it('installs a stub that points into node_modules', () => {
38
+ const project = mkdtempSync(join(tmpdir(), 'rstool-skills-'));
39
+ try {
40
+ execFileSync('node', [SCRIPT, 'install', '--dir', 'skills'], { cwd: project, encoding: 'utf8' });
41
+ const stub = readFileSync(join(project, 'skills/rstool/SKILL.md'), 'utf8');
42
+ expect(stub).toBe(renderStub('node_modules/@rsconcept/rstool/skills/rstool/SKILL.md'));
43
+ expect(stub).toContain('name: rstool');
44
+ } finally {
45
+ rmSync(project, { recursive: true, force: true });
46
+ }
47
+ });
48
+ });