@enki-dev-expert/global-engineering-standards 0.0.0-stage → 0.1.1
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/AGENTS.md +303 -0
- package/CHANGELOG.md +28 -0
- package/CITATION.cff +21 -0
- package/CODE_OF_CONDUCT.md +13 -0
- package/CONTRIBUTING.md +27 -0
- package/LICENSE +21 -0
- package/README.md +159 -2
- package/SECURITY.md +25 -0
- package/dist/cli.js +40 -0
- package/dist/content.js +71 -0
- package/dist/filesystem.js +83 -0
- package/dist/installer.js +243 -0
- package/dist/layout.js +42 -0
- package/dist/transaction.js +143 -0
- package/docs/ai-and-llm.md +1312 -0
- package/docs/architecture.md +1007 -0
- package/docs/cost-engineering.md +1322 -0
- package/docs/data.md +993 -0
- package/docs/governance.md +1744 -0
- package/docs/mobile-and-web.md +1362 -0
- package/docs/observability.md +1345 -0
- package/docs/platforms-and-applications.md +1581 -0
- package/docs/privacy.md +566 -0
- package/docs/quality-and-testing.md +1278 -0
- package/docs/scalability.md +1012 -0
- package/docs/security.md +373 -0
- package/handbook/claude-plugin.md +31 -0
- package/handbook/compatibility.md +27 -0
- package/handbook/recovery.md +29 -0
- package/handbook/releasing.md +50 -0
- package/handbook/security-review.md +41 -0
- package/handbook/signing.md +18 -0
- package/handbook/validation.md +50 -0
- package/package.json +65 -4
- package/scripts/content-baseline.json +25 -0
- package/skills/engineering-architecture-review/SKILL.md +1531 -0
- package/skills/engineering-cost-estimate/SKILL.md +1599 -0
- package/skills/engineering-database-review/SKILL.md +1364 -0
- package/skills/engineering-privacy-check/SKILL.md +1295 -0
- package/skills/engineering-safe-implementation/SKILL.md +1502 -0
- package/skills/engineering-security-standards-check/SKILL.md +1112 -0
- package/src/cli.ts +45 -0
- package/src/content.ts +82 -0
- package/src/filesystem.ts +94 -0
- package/src/installer.ts +280 -0
- package/src/layout.ts +70 -0
- package/src/transaction.ts +167 -0
- package/templates/ADR.md +1302 -0
- package/templates/AUDIT_REPORT.md +1693 -0
- package/templates/EXCEPTIONS.md +647 -0
- package/templates/PROJECT_AGENTS.md +868 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { closeSync, existsSync, openSync, readdirSync, unlinkSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {
|
|
5
|
+
asSnapshot,
|
|
6
|
+
assertSafe,
|
|
7
|
+
ensureDirectory,
|
|
8
|
+
equal,
|
|
9
|
+
replace,
|
|
10
|
+
type Snapshot,
|
|
11
|
+
snapshot,
|
|
12
|
+
textOf,
|
|
13
|
+
} from './filesystem.js';
|
|
14
|
+
import type { Layout } from './layout.js';
|
|
15
|
+
|
|
16
|
+
export interface Change {
|
|
17
|
+
key: string;
|
|
18
|
+
before: Snapshot | null;
|
|
19
|
+
after: Snapshot | null;
|
|
20
|
+
}
|
|
21
|
+
interface Journal {
|
|
22
|
+
schema: number;
|
|
23
|
+
id: string;
|
|
24
|
+
status: 'pending' | 'committed' | 'rolled-back';
|
|
25
|
+
changes: Change[];
|
|
26
|
+
}
|
|
27
|
+
const journalDir = (context: Layout) => path.join(context.state, 'transactions');
|
|
28
|
+
export function journals(context: Layout): string[] {
|
|
29
|
+
const directory = journalDir(context);
|
|
30
|
+
assertSafe(directory);
|
|
31
|
+
return existsSync(directory)
|
|
32
|
+
? readdirSync(directory)
|
|
33
|
+
.filter((x) => /^\d+-[a-f0-9-]+\.json$/.test(x))
|
|
34
|
+
.sort()
|
|
35
|
+
.reverse()
|
|
36
|
+
: [];
|
|
37
|
+
}
|
|
38
|
+
function validateSnapshot(value: Snapshot | null): void {
|
|
39
|
+
if (value === null) return;
|
|
40
|
+
if (
|
|
41
|
+
!value ||
|
|
42
|
+
typeof value.data !== 'string' ||
|
|
43
|
+
value.data.length > 12 * 1024 * 1024 ||
|
|
44
|
+
!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value.data) ||
|
|
45
|
+
!Number.isInteger(value.mode) ||
|
|
46
|
+
value.mode < 0 ||
|
|
47
|
+
value.mode > 0o777
|
|
48
|
+
)
|
|
49
|
+
throw new Error('Invalid transaction snapshot');
|
|
50
|
+
}
|
|
51
|
+
function readJournal(context: Layout, name: string): Journal {
|
|
52
|
+
const value = JSON.parse(textOf(snapshot(path.join(journalDir(context), name)))) as Journal;
|
|
53
|
+
if (
|
|
54
|
+
value.schema !== 1 ||
|
|
55
|
+
name !== `${value.id}.json` ||
|
|
56
|
+
!['pending', 'committed', 'rolled-back'].includes(value.status) ||
|
|
57
|
+
!Array.isArray(value.changes) ||
|
|
58
|
+
value.changes.length > context.targets.size
|
|
59
|
+
)
|
|
60
|
+
throw new Error('Invalid transaction journal');
|
|
61
|
+
const seen = new Set<string>();
|
|
62
|
+
for (const change of value.changes) {
|
|
63
|
+
if (!context.targets.has(change.key) || seen.has(change.key))
|
|
64
|
+
throw new Error('Invalid transaction target');
|
|
65
|
+
seen.add(change.key);
|
|
66
|
+
validateSnapshot(change.before);
|
|
67
|
+
validateSnapshot(change.after);
|
|
68
|
+
}
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
function save(context: Layout, journal: Journal): void {
|
|
72
|
+
const serialized = `${JSON.stringify(journal, null, 2)}\n`;
|
|
73
|
+
if (Buffer.byteLength(serialized) > 8 * 1024 * 1024)
|
|
74
|
+
throw new Error('Transaction backup exceeds the 8 MiB safety limit');
|
|
75
|
+
replace(path.join(journalDir(context), `${journal.id}.json`), asSnapshot(serialized));
|
|
76
|
+
}
|
|
77
|
+
export function pending(context: Layout): boolean {
|
|
78
|
+
return journals(context).some((name) => readJournal(context, name).status === 'pending');
|
|
79
|
+
}
|
|
80
|
+
export function locked<T>(context: Layout, work: () => T): T {
|
|
81
|
+
ensureDirectory(context.state);
|
|
82
|
+
const file = path.join(context.state, 'operation.lock');
|
|
83
|
+
assertSafe(file);
|
|
84
|
+
const fd = openSync(file, 'wx', 0o600);
|
|
85
|
+
try {
|
|
86
|
+
return work();
|
|
87
|
+
} finally {
|
|
88
|
+
closeSync(fd);
|
|
89
|
+
unlinkSync(file);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export function commit(context: Layout, changes: Change[], failAfter?: number): string | null {
|
|
93
|
+
if (!changes.length) return null;
|
|
94
|
+
if (pending(context))
|
|
95
|
+
throw new Error('An interrupted transaction exists. Run rollback before continuing.');
|
|
96
|
+
for (const change of changes) {
|
|
97
|
+
const file = context.targets.get(change.key);
|
|
98
|
+
if (!file || !equal(snapshot(file), change.before))
|
|
99
|
+
throw new Error(`Concurrent modification: ${change.key}`);
|
|
100
|
+
}
|
|
101
|
+
const journal: Journal = {
|
|
102
|
+
schema: 1,
|
|
103
|
+
id: `${Date.now()}-${randomUUID()}`,
|
|
104
|
+
status: 'pending',
|
|
105
|
+
changes,
|
|
106
|
+
};
|
|
107
|
+
ensureDirectory(journalDir(context));
|
|
108
|
+
save(context, journal);
|
|
109
|
+
const completed: Change[] = [];
|
|
110
|
+
try {
|
|
111
|
+
for (const change of changes) {
|
|
112
|
+
const file = context.targets.get(change.key) as string;
|
|
113
|
+
if (!equal(snapshot(file), change.before))
|
|
114
|
+
throw new Error(`Concurrent modification: ${change.key}`);
|
|
115
|
+
replace(file, change.after);
|
|
116
|
+
completed.push(change);
|
|
117
|
+
if (completed.length === failAfter) throw new Error('Injected transaction failure');
|
|
118
|
+
}
|
|
119
|
+
journal.status = 'committed';
|
|
120
|
+
save(context, journal);
|
|
121
|
+
return journal.id;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
for (const change of completed.reverse()) {
|
|
124
|
+
const file = context.targets.get(change.key) as string;
|
|
125
|
+
if (!equal(snapshot(file), change.after))
|
|
126
|
+
throw new Error('Rollback conflict; preserve the journal and recover manually.', {
|
|
127
|
+
cause: error,
|
|
128
|
+
});
|
|
129
|
+
replace(file, change.before);
|
|
130
|
+
}
|
|
131
|
+
journal.status = 'rolled-back';
|
|
132
|
+
save(context, journal);
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
export function rollback(context: Layout): { id: string; changes: number } {
|
|
137
|
+
const journal = journals(context)
|
|
138
|
+
.map((name) => readJournal(context, name))
|
|
139
|
+
.find((x) => x.status !== 'rolled-back');
|
|
140
|
+
if (!journal) throw new Error('No transaction to roll back.');
|
|
141
|
+
for (const change of journal.changes) {
|
|
142
|
+
const current = snapshot(context.targets.get(change.key) as string);
|
|
143
|
+
if (
|
|
144
|
+
!equal(current, change.after) &&
|
|
145
|
+
!(journal.status === 'pending' && equal(current, change.before))
|
|
146
|
+
) {
|
|
147
|
+
throw new Error(`Rollback would overwrite later edits: ${change.key}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (!context.options.dryRun) {
|
|
151
|
+
journal.status = 'pending';
|
|
152
|
+
save(context, journal);
|
|
153
|
+
for (const change of [...journal.changes].reverse()) {
|
|
154
|
+
const file = context.targets.get(change.key) as string;
|
|
155
|
+
const current = snapshot(file);
|
|
156
|
+
if (
|
|
157
|
+
!equal(current, change.after) &&
|
|
158
|
+
!(journal.status === 'pending' && equal(current, change.before))
|
|
159
|
+
)
|
|
160
|
+
throw new Error(`Concurrent modification: ${change.key}`);
|
|
161
|
+
replace(file, change.before);
|
|
162
|
+
}
|
|
163
|
+
journal.status = 'rolled-back';
|
|
164
|
+
save(context, journal);
|
|
165
|
+
}
|
|
166
|
+
return { id: journal.id, changes: journal.changes.length };
|
|
167
|
+
}
|