@trypattern/sdk 0.1.38 → 0.1.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bookings.d.ts +1 -7
- package/dist/bookingsStore.d.ts +1 -7
- package/dist/cli/app.js +71 -2
- package/dist/cli/guidance.d.ts +8 -0
- package/dist/cli/guidance.js +121 -0
- package/dist/cli/instructions.d.ts +21 -0
- package/dist/cli/instructions.js +206 -0
- package/dist/inquiriesStore.d.ts +1 -7
- package/dist/publishWire.d.ts +37 -59
- package/dist/publishWire.js +4 -2
- package/dist/requests.d.ts +14 -32
- package/dist/requests.js +12 -5
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +1 -0
- package/dist/server/inquiries.d.ts +7 -2
- package/dist/server/inquiries.js +2 -2
- package/dist/server/requestSources.d.ts +41 -0
- package/dist/server/requestSources.js +157 -0
- package/dist/server/requests.d.ts +7 -3
- package/dist/server/requests.js +130 -62
- package/docs/CHANGELOG.json +331 -0
- package/docs/PATTERN.md +87 -0
- package/docs/app.md +74 -0
- package/docs/data.md +109 -0
- package/docs/history/agents.json +1141 -0
- package/docs/look.md +147 -0
- package/docs/requests.md +115 -0
- package/docs/state.md +142 -0
- package/package.json +4 -3
package/dist/bookings.d.ts
CHANGED
|
@@ -88,13 +88,7 @@ export declare const bookingTables: {
|
|
|
88
88
|
readonly columns: {
|
|
89
89
|
readonly source: z.ZodString;
|
|
90
90
|
readonly requestId: z.ZodString;
|
|
91
|
-
readonly kind: z.
|
|
92
|
-
done: "done";
|
|
93
|
-
accepted: "accepted";
|
|
94
|
-
declined: "declined";
|
|
95
|
-
replied: "replied";
|
|
96
|
-
received: "received";
|
|
97
|
-
}>;
|
|
91
|
+
readonly kind: z.ZodString;
|
|
98
92
|
};
|
|
99
93
|
readonly access: {
|
|
100
94
|
readonly read: "server";
|
package/dist/bookingsStore.d.ts
CHANGED
|
@@ -305,13 +305,7 @@ export declare const bookings: {
|
|
|
305
305
|
readonly columns: {
|
|
306
306
|
readonly source: z.ZodString;
|
|
307
307
|
readonly requestId: z.ZodString;
|
|
308
|
-
readonly kind: z.
|
|
309
|
-
done: "done";
|
|
310
|
-
accepted: "accepted";
|
|
311
|
-
declined: "declined";
|
|
312
|
-
replied: "replied";
|
|
313
|
-
received: "received";
|
|
314
|
-
}>;
|
|
308
|
+
readonly kind: z.ZodString;
|
|
315
309
|
};
|
|
316
310
|
readonly access: {
|
|
317
311
|
readonly read: "server";
|
package/dist/cli/app.js
CHANGED
|
@@ -26,6 +26,8 @@ import { REQUESTS_USAGE, requests } from "./requests.js";
|
|
|
26
26
|
import { withStartingValues } from "./startingValues.js";
|
|
27
27
|
import { toolBin } from "./tools.js";
|
|
28
28
|
import { isNewer, latestPattern, newerPattern, updateNote } from "./updates.js";
|
|
29
|
+
import { REPORT_PATH, changelogIn, pendingUpdate, recordUpdate } from "./guidance.js";
|
|
30
|
+
import { PROPOSAL_PATH, instructionsNote, instructionsState, migrateInstructions } from "./instructions.js";
|
|
29
31
|
function dependsOnPattern(directory) {
|
|
30
32
|
try {
|
|
31
33
|
const manifest = JSON.parse(readFileSync(join(directory, 'package.json'), 'utf8'));
|
|
@@ -84,6 +86,9 @@ const SCREENSHOT_WORKER = join(SDK_SOURCE, 'cli', `screenshot${SDK_EXTENSION}`);
|
|
|
84
86
|
const VISUAL_URL_ENV = 'PATTERN_VISUAL_URL';
|
|
85
87
|
const GLOBAL_FLAGS = ['--json', '--help', '-h'];
|
|
86
88
|
const NUDGED_COMMANDS = new Set(['start', 'check', 'publish', 'data']);
|
|
89
|
+
const GUIDED_COMMANDS = new Set(['start', 'check']);
|
|
90
|
+
const SDK_PACKAGE = resolve(SDK_SOURCE, '..');
|
|
91
|
+
const INSTALLED_SDK = join(ROOT, 'node_modules', '@trypattern', 'sdk');
|
|
87
92
|
const VALUE_FLAGS = new Set([
|
|
88
93
|
'--commands',
|
|
89
94
|
'--name',
|
|
@@ -403,6 +408,16 @@ progress). New Pattern features on the live site, such as the owner approving ap
|
|
|
403
408
|
need that publish.`,
|
|
404
409
|
run: update
|
|
405
410
|
},
|
|
411
|
+
'migrate-instructions': {
|
|
412
|
+
summary: "Move an older AGENTS.md onto Pattern's guide, keeping what's about this site",
|
|
413
|
+
details: `Pattern's rules now ship inside Pattern (node_modules/@trypattern/sdk/docs/PATTERN.md), and
|
|
414
|
+
AGENTS.md imports them. This removes the sections of AGENTS.md that are Pattern's old template text,
|
|
415
|
+
unchanged, and keeps everything else. A section that mixes Pattern's rules with this project's files,
|
|
416
|
+
or one of Pattern's sections changed for this project, stays in AGENTS.md with a proposed replacement
|
|
417
|
+
in ${PROPOSAL_PATH}; decide each one, then delete that file. app check and app start say
|
|
418
|
+
"instructions migration incomplete" until it's done. Does nothing once AGENTS.md imports the guide.`,
|
|
419
|
+
run: instructions
|
|
420
|
+
},
|
|
406
421
|
versions: {
|
|
407
422
|
summary: 'List the versions that went live, newest first',
|
|
408
423
|
details: 'Each version with its number, whether it went live, when, and who published it. app restore <number> puts one back.',
|
|
@@ -1637,12 +1652,56 @@ async function update() {
|
|
|
1637
1652
|
text: `✗ pnpm couldn’t install Pattern ${latest}:\n${installed.output.trim().split('\n').slice(-LOG_TAIL_LINES).join('\n')}`
|
|
1638
1653
|
};
|
|
1639
1654
|
}
|
|
1655
|
+
const report = recordUpdate(ROOT, changelogIn(INSTALLED_SDK), current, latest);
|
|
1656
|
+
const instructions = instructionsNote(instructionsState(ROOT));
|
|
1640
1657
|
return {
|
|
1641
1658
|
code: 0,
|
|
1642
|
-
data: { current, latest, updated: true },
|
|
1643
|
-
text:
|
|
1659
|
+
data: { current, latest, updated: true, report: REPORT_PATH },
|
|
1660
|
+
text: [
|
|
1661
|
+
`✓ Updated Pattern ${current} → ${latest}`,
|
|
1662
|
+
'',
|
|
1663
|
+
report,
|
|
1664
|
+
` saved ${REPORT_PATH}`,
|
|
1665
|
+
` next run app migration pattern_${latest.replaceAll('.', '_')} (it writes nothing when Pattern's own tables didn't change), do what the report says, then app check, then app publish --message 'Updated Pattern' (straight away if nothing else is in progress) so the live site gets it`,
|
|
1666
|
+
...(instructions === undefined ? [] : [instructions])
|
|
1667
|
+
].join('\n')
|
|
1644
1668
|
};
|
|
1645
1669
|
}
|
|
1670
|
+
function instructions() {
|
|
1671
|
+
const result = migrateInstructions(ROOT, join(SDK_PACKAGE, 'docs', 'history', 'agents.json'));
|
|
1672
|
+
if (result.status === 'none') {
|
|
1673
|
+
return {
|
|
1674
|
+
code: 1,
|
|
1675
|
+
data: result,
|
|
1676
|
+
text: '✗ There is no AGENTS.md here to move onto the guide.'
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
if (result.removed === 0 && result.proposed === 0) {
|
|
1680
|
+
return {
|
|
1681
|
+
code: 0,
|
|
1682
|
+
data: result,
|
|
1683
|
+
text: result.status === 'proposed'
|
|
1684
|
+
? `✓ AGENTS.md already imports Pattern's guide. Some sections still need a decision: follow ${PROPOSAL_PATH}, then delete it.`
|
|
1685
|
+
: "✓ AGENTS.md already imports Pattern's guide."
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
return {
|
|
1689
|
+
code: 0,
|
|
1690
|
+
data: result,
|
|
1691
|
+
text: [
|
|
1692
|
+
`✓ AGENTS.md now imports Pattern's guide (node_modules/@trypattern/sdk/docs/PATTERN.md); ${result.removed} section${result.removed === 1 ? '' : 's'} of Pattern's old rules removed.`,
|
|
1693
|
+
result.proposed === 0
|
|
1694
|
+
? ' next read node_modules/@trypattern/sdk/docs/PATTERN.md now: it replaces the rules you had.'
|
|
1695
|
+
: ` next ${result.proposed} section${result.proposed === 1 ? '' : 's'} may hold something written for this project: follow ${PROPOSAL_PATH}, then delete it. Read node_modules/@trypattern/sdk/docs/PATTERN.md first: it replaces the rules you had.`
|
|
1696
|
+
].join('\n')
|
|
1697
|
+
};
|
|
1698
|
+
}
|
|
1699
|
+
function guidanceNotes() {
|
|
1700
|
+
return [
|
|
1701
|
+
pendingUpdate(ROOT, changelogIn(SDK_PACKAGE), sdkVersion()),
|
|
1702
|
+
instructionsNote(instructionsState(ROOT))
|
|
1703
|
+
].filter((note) => note !== undefined);
|
|
1704
|
+
}
|
|
1646
1705
|
async function setup() {
|
|
1647
1706
|
const pins = readPins();
|
|
1648
1707
|
const haveNode = process.versions.node;
|
|
@@ -1796,6 +1855,16 @@ async function main(argv) {
|
|
|
1796
1855
|
};
|
|
1797
1856
|
}
|
|
1798
1857
|
}
|
|
1858
|
+
if (GUIDED_COMMANDS.has(name)) {
|
|
1859
|
+
const notes = guidanceNotes();
|
|
1860
|
+
if (notes.length > 0) {
|
|
1861
|
+
outcome = {
|
|
1862
|
+
...outcome,
|
|
1863
|
+
data: { ...outcome.data, guidance: notes },
|
|
1864
|
+
text: [outcome.text, ...notes].join('\n')
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1799
1868
|
}
|
|
1800
1869
|
if (jsonMode) {
|
|
1801
1870
|
const payload = {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const GUIDE_PATH = "node_modules/@trypattern/sdk/docs/PATTERN.md";
|
|
2
|
+
export declare const REPORT_PATH: string;
|
|
3
|
+
export declare function changelogIn(directory: string): string;
|
|
4
|
+
export declare function updateReport(path: string, from: string, to: string): string;
|
|
5
|
+
export declare function patternFolder(root: string): string;
|
|
6
|
+
export declare function recordUpdate(root: string, changelogPath: string, from: string, to: string): string;
|
|
7
|
+
export declare function updatedNote(from: string, to: string): string;
|
|
8
|
+
export declare function pendingUpdate(root: string, changelogPath: string, current: string): string | undefined;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { isNewer } from "./updates.js";
|
|
6
|
+
export const GUIDE_PATH = 'node_modules/@trypattern/sdk/docs/PATTERN.md';
|
|
7
|
+
export const REPORT_PATH = join('.pattern', 'update-report.md');
|
|
8
|
+
const SEEN_PATH = join('.pattern', 'update.json');
|
|
9
|
+
const visualChange = z.object({
|
|
10
|
+
what: z.string(),
|
|
11
|
+
before: z.string(),
|
|
12
|
+
after: z.string(),
|
|
13
|
+
keep: z.string()
|
|
14
|
+
});
|
|
15
|
+
const release = z.object({
|
|
16
|
+
version: z.string(),
|
|
17
|
+
added: z.array(z.string()),
|
|
18
|
+
changed: z.array(z.string()),
|
|
19
|
+
visual: z.array(visualChange),
|
|
20
|
+
migrations: z.array(z.string())
|
|
21
|
+
});
|
|
22
|
+
const changelog = z.object({ versions: z.array(release) });
|
|
23
|
+
const seen = z.object({ reported: z.string() });
|
|
24
|
+
export function changelogIn(directory) {
|
|
25
|
+
return join(directory, 'docs', 'CHANGELOG.json');
|
|
26
|
+
}
|
|
27
|
+
function readJson(path) {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function releasesBetween(path, from, to) {
|
|
36
|
+
const parsed = changelog.safeParse(readJson(path));
|
|
37
|
+
if (!parsed.success)
|
|
38
|
+
return [];
|
|
39
|
+
return parsed.data.versions
|
|
40
|
+
.filter((entry) => isNewer(entry.version, from) && !isNewer(entry.version, to))
|
|
41
|
+
.sort((a, b) => isNewer(a.version, b.version) ? 1 : isNewer(b.version, a.version) ? -1 : 0);
|
|
42
|
+
}
|
|
43
|
+
function listed(heading, releases, lines) {
|
|
44
|
+
const items = releases.flatMap((entry) => lines(entry).map((line) => `- ${entry.version} · ${line}`));
|
|
45
|
+
return items.length === 0 ? [] : [`## ${heading}`, '', ...items, ''];
|
|
46
|
+
}
|
|
47
|
+
export function updateReport(path, from, to) {
|
|
48
|
+
const releases = releasesBetween(path, from, to);
|
|
49
|
+
return [
|
|
50
|
+
`# Pattern ${from} → ${to}`,
|
|
51
|
+
'',
|
|
52
|
+
`Pattern was updated. Reread ${GUIDE_PATH} and the references you rely on: they changed with Pattern, and what you remember of them is from ${from}.`,
|
|
53
|
+
'',
|
|
54
|
+
...listed('Do these now', releases, (entry) => entry.migrations),
|
|
55
|
+
...(releases.some((entry) => entry.migrations.length > 0)
|
|
56
|
+
? [
|
|
57
|
+
'Then run app check, and publish when nothing else is in progress.',
|
|
58
|
+
''
|
|
59
|
+
]
|
|
60
|
+
: []),
|
|
61
|
+
...listed('Looks different', releases, (entry) => entry.visual.map((change) => `${change.what}: ${change.before} Now: ${change.after}${change.keep === '' ? '' : ` To keep the old look: ${change.keep}`}`)),
|
|
62
|
+
...listed('New', releases, (entry) => entry.added),
|
|
63
|
+
...listed('Changed', releases, (entry) => entry.changed),
|
|
64
|
+
...(releases.length === 0
|
|
65
|
+
? ['Nothing in this update needs anything from you.', '']
|
|
66
|
+
: [])
|
|
67
|
+
].join('\n');
|
|
68
|
+
}
|
|
69
|
+
export function patternFolder(root) {
|
|
70
|
+
const folder = join(root, '.pattern');
|
|
71
|
+
mkdirSync(folder, { recursive: true });
|
|
72
|
+
writeFileSync(join(folder, '.gitignore'), '*\n');
|
|
73
|
+
return folder;
|
|
74
|
+
}
|
|
75
|
+
function remember(root, version) {
|
|
76
|
+
patternFolder(root);
|
|
77
|
+
writeFileSync(join(root, SEEN_PATH), `${JSON.stringify({ reported: version }, null, 2)}\n`);
|
|
78
|
+
}
|
|
79
|
+
export function recordUpdate(root, changelogPath, from, to) {
|
|
80
|
+
const report = updateReport(changelogPath, from, to);
|
|
81
|
+
patternFolder(root);
|
|
82
|
+
writeFileSync(join(root, REPORT_PATH), report);
|
|
83
|
+
remember(root, to);
|
|
84
|
+
return report;
|
|
85
|
+
}
|
|
86
|
+
function committedVersion(root) {
|
|
87
|
+
const shown = spawnSync('git', ['show', 'HEAD:package.json'], {
|
|
88
|
+
cwd: root,
|
|
89
|
+
encoding: 'utf8'
|
|
90
|
+
});
|
|
91
|
+
if (shown.status !== 0)
|
|
92
|
+
return undefined;
|
|
93
|
+
try {
|
|
94
|
+
const manifest = JSON.parse(shown.stdout);
|
|
95
|
+
const dependencies = Reflect.get(Object(manifest), 'dependencies');
|
|
96
|
+
const version = Reflect.get(Object(dependencies), '@trypattern/sdk');
|
|
97
|
+
const exact = typeof version === 'string' ? version.replace(/^[\^~]/, '') : '';
|
|
98
|
+
return /^\d+\.\d+\.\d+/.test(exact) ? exact : undefined;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export function updatedNote(from, to) {
|
|
105
|
+
return ` updated Pattern ${from} → ${to}. Read ${REPORT_PATH} (what's new, what looks different and what to do now), do what it says, and reread ${GUIDE_PATH}: it changed with Pattern.`;
|
|
106
|
+
}
|
|
107
|
+
export function pendingUpdate(root, changelogPath, current) {
|
|
108
|
+
const remembered = seen.safeParse(readJson(join(root, SEEN_PATH)));
|
|
109
|
+
if (remembered.success && !isNewer(current, remembered.data.reported)) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
const from = remembered.success
|
|
113
|
+
? remembered.data.reported
|
|
114
|
+
: committedVersion(root);
|
|
115
|
+
if (from === undefined || !isNewer(current, from)) {
|
|
116
|
+
remember(root, current);
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
recordUpdate(root, changelogPath, from, current);
|
|
120
|
+
return updatedNote(from, current);
|
|
121
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const GUIDE_IMPORT = "@node_modules/@trypattern/sdk/docs/PATTERN.md";
|
|
2
|
+
export declare const PROPOSAL_PATH: string;
|
|
3
|
+
type Section = {
|
|
4
|
+
raw: string;
|
|
5
|
+
text: string;
|
|
6
|
+
normalized: string;
|
|
7
|
+
};
|
|
8
|
+
export type InstructionsState = 'none' | 'migrated' | 'proposed' | 'old';
|
|
9
|
+
export declare function sectionsOf(markdown: string): Section[];
|
|
10
|
+
export declare function instructionsState(root: string): InstructionsState;
|
|
11
|
+
export declare function instructionsNote(state: InstructionsState): string | undefined;
|
|
12
|
+
export declare function migrateInstructions(root: string, historyPath: string): {
|
|
13
|
+
status: "none";
|
|
14
|
+
removed: number;
|
|
15
|
+
proposed: number;
|
|
16
|
+
} | {
|
|
17
|
+
status: "migrated" | "proposed";
|
|
18
|
+
removed: number;
|
|
19
|
+
proposed: number;
|
|
20
|
+
};
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { GUIDE_PATH, patternFolder } from "./guidance.js";
|
|
6
|
+
export const GUIDE_IMPORT = `@${GUIDE_PATH}`;
|
|
7
|
+
export const PROPOSAL_PATH = join('.pattern', 'instructions-migration.md');
|
|
8
|
+
const GUIDE_PARAGRAPH = `Pattern's own rules (how to build, check and publish, and everything Pattern already does) come\nwith Pattern itself, so they stay current when it updates. Read them first, and reread them after\nPattern updates:\n\n${GUIDE_IMPORT}`;
|
|
9
|
+
const PROJECT_PATH = /`(?:src|server|starter|flows|public)\/|`(?:seed|schema)\.ts`|`wrangler\.jsonc`|`pattern\.json`/;
|
|
10
|
+
const owner = z.enum(['pattern', 'project', 'mixed']);
|
|
11
|
+
const history = z.object({
|
|
12
|
+
openers: z.array(z.object({ prefix: z.string(), owner, reference: z.string() })),
|
|
13
|
+
sections: z.record(z.string(), owner),
|
|
14
|
+
sentences: z.array(z.string())
|
|
15
|
+
});
|
|
16
|
+
function hashOf(text) {
|
|
17
|
+
return createHash('sha256').update(text).digest('hex').slice(0, 16);
|
|
18
|
+
}
|
|
19
|
+
function normalized(text) {
|
|
20
|
+
const collapsed = text.replace(/\s+/g, ' ').trim();
|
|
21
|
+
return collapsed.startsWith('- ') ? collapsed.slice(2) : collapsed;
|
|
22
|
+
}
|
|
23
|
+
function sentencesOf(text) {
|
|
24
|
+
return text.split(/(?<=[.!?;:])\s+/).filter((sentence) => sentence !== '');
|
|
25
|
+
}
|
|
26
|
+
function startsSection(line, previous) {
|
|
27
|
+
return (line.startsWith('- ') ||
|
|
28
|
+
/^#{1,6} /.test(line) ||
|
|
29
|
+
(line !== '' && !/^\s/.test(line) && previous === ''));
|
|
30
|
+
}
|
|
31
|
+
export function sectionsOf(markdown) {
|
|
32
|
+
const groups = [];
|
|
33
|
+
let previous = '';
|
|
34
|
+
for (const line of markdown.split('\n')) {
|
|
35
|
+
const current = groups.at(-1);
|
|
36
|
+
if (current === undefined || startsSection(line, previous)) {
|
|
37
|
+
groups.push([line]);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
current.push(line);
|
|
41
|
+
}
|
|
42
|
+
previous = line;
|
|
43
|
+
}
|
|
44
|
+
return groups
|
|
45
|
+
.map((lines) => `${lines.join('\n')}\n`)
|
|
46
|
+
.filter((raw) => raw.trim() !== '')
|
|
47
|
+
.map((raw) => {
|
|
48
|
+
const text = raw.replace(/\n+$/, '');
|
|
49
|
+
return { raw, text, normalized: normalized(text) };
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function readHistory(path) {
|
|
53
|
+
try {
|
|
54
|
+
const parsed = history.safeParse(JSON.parse(readFileSync(path, 'utf8')));
|
|
55
|
+
return parsed.success ? parsed.data : undefined;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function openerOf(known, section) {
|
|
62
|
+
return known.openers.find((entry) => section.normalized.startsWith(entry.prefix));
|
|
63
|
+
}
|
|
64
|
+
function keptSentences(known, section, isEdited) {
|
|
65
|
+
const template = new Set(known.sentences);
|
|
66
|
+
return sentencesOf(section.normalized)
|
|
67
|
+
.filter((sentence) => (isEdited && !template.has(hashOf(sentence))) ||
|
|
68
|
+
PROJECT_PATH.test(sentence))
|
|
69
|
+
.join(' ');
|
|
70
|
+
}
|
|
71
|
+
function decide(known, section) {
|
|
72
|
+
const opener = openerOf(known, section);
|
|
73
|
+
const recorded = known.sections[hashOf(section.normalized)];
|
|
74
|
+
if (recorded === 'pattern')
|
|
75
|
+
return { kind: 'remove' };
|
|
76
|
+
if (recorded === 'project')
|
|
77
|
+
return { kind: 'keep' };
|
|
78
|
+
if (recorded === 'mixed') {
|
|
79
|
+
return {
|
|
80
|
+
kind: 'propose',
|
|
81
|
+
why: 'mixed',
|
|
82
|
+
reference: opener?.reference ?? 'PATTERN.md',
|
|
83
|
+
replacement: keptSentences(known, section, false)
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (opener === undefined || opener.owner === 'project') {
|
|
87
|
+
return { kind: 'keep' };
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
kind: 'propose',
|
|
91
|
+
why: 'edited',
|
|
92
|
+
reference: opener.reference,
|
|
93
|
+
replacement: keptSentences(known, section, true)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function isIntro(section) {
|
|
97
|
+
return section.text.startsWith('# ') || !/^(?:- |#)/.test(section.text);
|
|
98
|
+
}
|
|
99
|
+
function joined(chunks) {
|
|
100
|
+
return chunks.reduce((text, chunk) => {
|
|
101
|
+
const needsGap = text !== '' && !text.endsWith('\n\n') && !chunk.startsWith('- ');
|
|
102
|
+
return `${text}${needsGap ? '\n' : ''}${chunk}`;
|
|
103
|
+
}, '');
|
|
104
|
+
}
|
|
105
|
+
function withGuide(kept) {
|
|
106
|
+
const first = kept.findIndex((section) => !isIntro(section));
|
|
107
|
+
const at = first === -1 ? kept.length : first;
|
|
108
|
+
return joined([
|
|
109
|
+
...kept.slice(0, at).map((section) => section.raw),
|
|
110
|
+
`${GUIDE_PARAGRAPH}\n\n`,
|
|
111
|
+
...kept.slice(at).map((section) => section.raw)
|
|
112
|
+
]).replace(/\n+$/, '\n');
|
|
113
|
+
}
|
|
114
|
+
function proposalFor(proposals) {
|
|
115
|
+
const items = proposals.flatMap(({ section, decision }, index) => {
|
|
116
|
+
if (decision.kind !== 'propose')
|
|
117
|
+
return [];
|
|
118
|
+
const why = decision.why === 'mixed'
|
|
119
|
+
? `This is the old template's text. Its rules now live in Pattern's guide (node_modules/@trypattern/sdk/docs/${decision.reference}); only the lines about this project's own files belong in AGENTS.md.`
|
|
120
|
+
: `This looks like one of Pattern's old sections, changed for this project. Pattern's version now lives in node_modules/@trypattern/sdk/docs/${decision.reference}; keep only what's true and specific to this project.`;
|
|
121
|
+
return [
|
|
122
|
+
`## ${index + 1}. ${section.normalized.slice(0, 60)}…`,
|
|
123
|
+
'',
|
|
124
|
+
why,
|
|
125
|
+
'',
|
|
126
|
+
'Now in AGENTS.md:',
|
|
127
|
+
'',
|
|
128
|
+
'```md',
|
|
129
|
+
section.text,
|
|
130
|
+
'```',
|
|
131
|
+
'',
|
|
132
|
+
decision.replacement === ''
|
|
133
|
+
? 'Proposed: delete it.'
|
|
134
|
+
: 'Proposed replacement:',
|
|
135
|
+
'',
|
|
136
|
+
...(decision.replacement === ''
|
|
137
|
+
? []
|
|
138
|
+
: ['```md', `- ${decision.replacement}`, '```', ''])
|
|
139
|
+
];
|
|
140
|
+
});
|
|
141
|
+
return [
|
|
142
|
+
'# Finish moving AGENTS.md onto Pattern’s guide',
|
|
143
|
+
'',
|
|
144
|
+
`app migrate-instructions moved AGENTS.md onto Pattern's guide (${GUIDE_PATH}) and removed the rules Pattern now ships. It kept the sections below, because they may hold something written for this project. For each one, read it next to the guide, then apply the proposal, change it, or keep the section as it is. Check every fact against the project's files before keeping it.`,
|
|
145
|
+
'',
|
|
146
|
+
'When every section is decided, delete this file. app check and app start say "instructions migration incomplete" until it is gone.',
|
|
147
|
+
'',
|
|
148
|
+
...items
|
|
149
|
+
].join('\n');
|
|
150
|
+
}
|
|
151
|
+
export function instructionsState(root) {
|
|
152
|
+
const path = join(root, 'AGENTS.md');
|
|
153
|
+
if (!existsSync(path))
|
|
154
|
+
return 'none';
|
|
155
|
+
if (!readFileSync(path, 'utf8').includes(GUIDE_IMPORT))
|
|
156
|
+
return 'old';
|
|
157
|
+
return existsSync(join(root, PROPOSAL_PATH)) ? 'proposed' : 'migrated';
|
|
158
|
+
}
|
|
159
|
+
export function instructionsNote(state) {
|
|
160
|
+
if (state === 'old') {
|
|
161
|
+
return " instructions migration incomplete AGENTS.md still carries Pattern's old rules, so it may say Pattern can't do things it now does. Run app migrate-instructions and follow what it prints.";
|
|
162
|
+
}
|
|
163
|
+
if (state === 'proposed') {
|
|
164
|
+
return ` instructions migration incomplete Some sections of AGENTS.md need a decision: follow ${PROPOSAL_PATH}, then delete it.`;
|
|
165
|
+
}
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
export function migrateInstructions(root, historyPath) {
|
|
169
|
+
const path = join(root, 'AGENTS.md');
|
|
170
|
+
const state = instructionsState(root);
|
|
171
|
+
if (state === 'none') {
|
|
172
|
+
return { status: 'none', removed: 0, proposed: 0 };
|
|
173
|
+
}
|
|
174
|
+
if (state !== 'old') {
|
|
175
|
+
return { status: state, removed: 0, proposed: 0 };
|
|
176
|
+
}
|
|
177
|
+
const known = readHistory(historyPath);
|
|
178
|
+
if (known === undefined) {
|
|
179
|
+
throw new Error(`Pattern's instructions history is missing (${historyPath})`);
|
|
180
|
+
}
|
|
181
|
+
const decided = sectionsOf(readFileSync(path, 'utf8')).map((section) => ({
|
|
182
|
+
section,
|
|
183
|
+
decision: decide(known, section)
|
|
184
|
+
}));
|
|
185
|
+
const kept = decided
|
|
186
|
+
.filter(({ decision }) => decision.kind !== 'remove')
|
|
187
|
+
.map(({ section }) => section);
|
|
188
|
+
const proposals = decided.filter(({ decision }) => decision.kind === 'propose');
|
|
189
|
+
writeFileSync(path, withGuide(kept));
|
|
190
|
+
if (!existsSync(join(root, 'CLAUDE.md'))) {
|
|
191
|
+
writeFileSync(join(root, 'CLAUDE.md'), '@AGENTS.md\n');
|
|
192
|
+
}
|
|
193
|
+
const proposalPath = join(root, PROPOSAL_PATH);
|
|
194
|
+
if (proposals.length === 0) {
|
|
195
|
+
rmSync(proposalPath, { force: true });
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
patternFolder(root);
|
|
199
|
+
writeFileSync(proposalPath, proposalFor(proposals));
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
status: proposals.length === 0 ? 'migrated' : 'proposed',
|
|
203
|
+
removed: decided.length - kept.length,
|
|
204
|
+
proposed: proposals.length
|
|
205
|
+
};
|
|
206
|
+
}
|
package/dist/inquiriesStore.d.ts
CHANGED
|
@@ -56,13 +56,7 @@ export declare const inquiries: {
|
|
|
56
56
|
readonly columns: {
|
|
57
57
|
readonly source: z.ZodString;
|
|
58
58
|
readonly requestId: z.ZodString;
|
|
59
|
-
readonly kind: z.
|
|
60
|
-
done: "done";
|
|
61
|
-
accepted: "accepted";
|
|
62
|
-
declined: "declined";
|
|
63
|
-
replied: "replied";
|
|
64
|
-
received: "received";
|
|
65
|
-
}>;
|
|
59
|
+
readonly kind: z.ZodString;
|
|
66
60
|
};
|
|
67
61
|
readonly access: {
|
|
68
62
|
readonly read: "server";
|