create-filegrc 0.3.0 → 0.3.2
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 +28 -1
- package/package.json +1 -1
- package/src/cli.js +99 -6
- package/src/defaults.js +12 -7
- package/src/index.js +219 -21
- package/template/AGENTS.md +8 -22
- package/template/README.md +36 -85
- package/template/WORKSPACE.md +5 -14
- package/template/data/AGENTS.md +2 -2
- package/template/data/evidence/AGENTS.md +1 -1
- package/template/data/workspace.json +2 -2
- package/template/package.json +1 -1
- package/template-parameters.json +32 -0
package/README.md
CHANGED
|
@@ -11,10 +11,37 @@ npm run serve
|
|
|
11
11
|
|
|
12
12
|
Setup asks for the legal organization name, the initial policy owner and their email, a security reporting address, and the program timezone. The generated private project includes a starter Security program, Program Readiness, a policy-driven Work Queue, Policy Events that add linked tasks, later audit preparation, evidence packets, and one dependency: `filegrc`.
|
|
13
13
|
|
|
14
|
+
Creation has two layers. The five-record foundation contains workspace settings, the initial owner, the oversight team, renderer settings, and the filegrc system of record. The default `security` starter adds the framework references, proposed policies, controls, obligations, documents, and training records. Pass `--starter foundation` when you want to stop before selecting a framework.
|
|
15
|
+
|
|
16
|
+
For one noninteractive run, pass company and service fields together or use `--config setup.json`. The optional `setup` object defines the service boundary and management goal after installation. Use `--filegrc-package <directory>` to exercise unpublished local engine changes instead of installing the registry release. This writes a machine-local `file:` dependency, so replace it with a released version before sharing the generated workspace.
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"companyName": "Example Company",
|
|
21
|
+
"policyOwnerName": "Security Owner",
|
|
22
|
+
"policyOwnerEmail": "owner@example.com",
|
|
23
|
+
"securityContactEmail": "security@example.com",
|
|
24
|
+
"timezone": "America/Chicago",
|
|
25
|
+
"starter": "security",
|
|
26
|
+
"setup": {
|
|
27
|
+
"serviceName": "Example Service",
|
|
28
|
+
"boundary": "The production service and supporting infrastructure.",
|
|
29
|
+
"criticality": "high",
|
|
30
|
+
"dataClassification": "Confidential",
|
|
31
|
+
"internetExposed": true,
|
|
32
|
+
"programGoal": "type-2"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
npx create-filegrc@latest company-grc --config setup.json
|
|
39
|
+
```
|
|
40
|
+
|
|
14
41
|
Generated workspaces also include layered `AGENTS.md` instructions and model-driven headless commands. `filegrc program-path` gives agents the same six steps, exact page guidance, current status, and next actions shown in the renderer. Agents can define program scope, approve policies, implement controls, test External Evidence, complete policy work, trigger event tasks, and prepare later audit packets through the same domain functions used by the renderer.
|
|
15
42
|
|
|
16
43
|
Git is initialized when needed. The browser can create local commits before a remote is configured.
|
|
17
44
|
|
|
18
45
|
Creation output reports the resolved filegrc version, program timezone, starter record counts, whether installation ran, and whether the target joined an existing Git worktree or received a new repository.
|
|
19
46
|
|
|
20
|
-
Use `npx create-filegrc@latest --help` for
|
|
47
|
+
Use `npx create-filegrc@latest --help` for noninteractive options.
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1,17 +1,45 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
2
3
|
import { createFilegrc } from "./index.js";
|
|
3
4
|
|
|
4
5
|
export async function runCli(argv = process.argv.slice(2)) {
|
|
5
|
-
const
|
|
6
|
+
const parsed = parseArgs(argv);
|
|
7
|
+
const config = parsed.options.config ? await readConfig(parsed.options.config) : {};
|
|
8
|
+
const organization = config.organization && typeof config.organization === "object"
|
|
9
|
+
? config.organization
|
|
10
|
+
: {};
|
|
11
|
+
const options = {
|
|
12
|
+
...organization,
|
|
13
|
+
...config,
|
|
14
|
+
...parsed.options,
|
|
15
|
+
setup: {
|
|
16
|
+
...(config.setup || {}),
|
|
17
|
+
...parsed.setup
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
delete options.organization;
|
|
21
|
+
delete options.config;
|
|
22
|
+
delete options.target;
|
|
23
|
+
if (!Object.keys(options.setup).length) delete options.setup;
|
|
24
|
+
const target = parsed.target ?? config.target ?? "filegrc-program";
|
|
6
25
|
if (options.help) return printHelp();
|
|
7
26
|
if (options.version) return printVersion();
|
|
8
27
|
const result = await createFilegrc({ target, ...options });
|
|
9
28
|
console.log(`Created ${result.target}`);
|
|
10
|
-
console.log(
|
|
29
|
+
console.log(
|
|
30
|
+
`filegrc ${result.engineVersion}${result.enginePackage ? ` from ${result.enginePackage}` : ""}: ` +
|
|
31
|
+
`${result.install === "installed" ? "installed" : "installation skipped"}`
|
|
32
|
+
);
|
|
11
33
|
console.log(`Git: ${result.gitMode === "existing-worktree" ? "joined existing worktree" : "initialized new repository"}`);
|
|
34
|
+
if (result.gitDetached) {
|
|
35
|
+
console.log("Warning: detached HEAD detected. Check out a branch before using browser commit, pull, or push.");
|
|
36
|
+
}
|
|
12
37
|
console.log(`Timezone: ${result.values.timezone}`);
|
|
38
|
+
for (const stage of result.stages) {
|
|
39
|
+
console.log(`Stage ${stage.id}: ${stage.status}${stage.status === "created" ? ` (${stage.records} records)` : ""}`);
|
|
40
|
+
}
|
|
13
41
|
console.log(
|
|
14
|
-
|
|
42
|
+
`${result.setup ? "Workspace after setup" : "Program baseline"}: ${result.resourceCounts.total} records, including ` +
|
|
15
43
|
`${result.resourceCounts.byType.requirement || 0} requirements, ` +
|
|
16
44
|
`${result.resourceCounts.byType.control || 0} controls, and ` +
|
|
17
45
|
`${result.resourceCounts.byType.obligation || 0} obligations.`
|
|
@@ -19,20 +47,40 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
19
47
|
console.log("");
|
|
20
48
|
console.log(` cd ${shellQuote(result.target)}`);
|
|
21
49
|
if (options.install === false) console.log(" npm install");
|
|
22
|
-
console.log(" npx filegrc setup");
|
|
50
|
+
if (!result.setup) console.log(" npx filegrc setup");
|
|
51
|
+
if (result.setup) console.log(" npx filegrc program-path --next --json");
|
|
23
52
|
console.log(" npm run validate");
|
|
24
53
|
console.log(" npm run serve");
|
|
25
54
|
console.log("");
|
|
26
|
-
|
|
55
|
+
if (result.setup) {
|
|
56
|
+
console.log(`Service setup: ${result.setup.system.id} (${result.setup.system.status}), target ${result.setup.target.assuranceGoal}.`);
|
|
57
|
+
if (result.setup.draft) {
|
|
58
|
+
console.log("Planned and in scope means selected for scope review, not approved or active.");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
console.log("Immediate human decisions:");
|
|
62
|
+
console.log(result.setup?.target.assuranceGoal && result.setup.target.assuranceGoal !== "none"
|
|
63
|
+
? ` 1. Confirm the selected assurance goal with management: ${assuranceGoalLabel(result.setup.target.assuranceGoal)}.`
|
|
64
|
+
: " 1. Select and confirm the assurance goal.");
|
|
65
|
+
console.log(" 2. Appoint an independent reviewer who is separate from the policy owner.");
|
|
66
|
+
console.log("Review the generated records, then commit the approved baseline.");
|
|
27
67
|
}
|
|
28
68
|
|
|
29
69
|
function shellQuote(value) {
|
|
30
70
|
return `'${String(value).replaceAll("'", "'\\''")}'`;
|
|
31
71
|
}
|
|
32
72
|
|
|
73
|
+
function assuranceGoalLabel(value) {
|
|
74
|
+
if (value === "soc-2-type-1") return "SOC 2 Type 1";
|
|
75
|
+
if (value === "soc-2-type-2") return "SOC 2 Type 2";
|
|
76
|
+
if (value === "readiness") return "Program Readiness";
|
|
77
|
+
return "No assurance goal selected";
|
|
78
|
+
}
|
|
79
|
+
|
|
33
80
|
function parseArgs(argv) {
|
|
34
81
|
let target;
|
|
35
82
|
const options = {};
|
|
83
|
+
const setup = {};
|
|
36
84
|
for (let index = 0; index < argv.length; index += 1) {
|
|
37
85
|
const value = argv[index];
|
|
38
86
|
if (!value.startsWith("-") && !target) {
|
|
@@ -46,15 +94,26 @@ function parseArgs(argv) {
|
|
|
46
94
|
else if (name === "yes" || name === "y") options.yes = true;
|
|
47
95
|
else if (name === "force") options.force = true;
|
|
48
96
|
else if (name === "no-install") options.install = false;
|
|
97
|
+
else if (name === "config") options.config = next();
|
|
49
98
|
else if (name === "company-name") options.companyName = next();
|
|
50
99
|
else if (name === "policy-owner-name") options.policyOwnerName = next();
|
|
51
100
|
else if (name === "policy-owner-email") options.policyOwnerEmail = next();
|
|
52
101
|
else if (name === "security-contact-email") options.securityContactEmail = next();
|
|
53
102
|
else if (name === "timezone") options.timezone = next();
|
|
103
|
+
else if (name === "starter") options.starter = next();
|
|
54
104
|
else if (name === "filegrc-version") options.filegrcVersion = next();
|
|
105
|
+
else if (name === "filegrc-package") options.filegrcPackage = next();
|
|
106
|
+
else if (name === "service-name") setup.serviceName = next();
|
|
107
|
+
else if (name === "boundary") setup.boundary = next();
|
|
108
|
+
else if (name === "service-owner") setup.ownerId = next();
|
|
109
|
+
else if (name === "criticality") setup.criticality = next();
|
|
110
|
+
else if (name === "classification") setup.dataClassification = next();
|
|
111
|
+
else if (name === "internet-exposed") setup.internetExposed = booleanOption(next(), "internet-exposed");
|
|
112
|
+
else if (name === "program-goal") setup.programGoal = next();
|
|
113
|
+
else if (name === "setup-draft") setup.draft = true;
|
|
55
114
|
else throw new Error(`Unknown option "${value}".`);
|
|
56
115
|
}
|
|
57
|
-
return { target
|
|
116
|
+
return { target, options, setup };
|
|
58
117
|
}
|
|
59
118
|
|
|
60
119
|
function printHelp() {
|
|
@@ -63,12 +122,23 @@ function printHelp() {
|
|
|
63
122
|
Create a filegrc workspace for a SOC 2 program.
|
|
64
123
|
|
|
65
124
|
Options:
|
|
125
|
+
--config <json-file|-> Read creation and optional setup values
|
|
66
126
|
--company-name <legal-name> Legal organization name
|
|
67
127
|
--policy-owner-name <name> Initial policy owner
|
|
68
128
|
--policy-owner-email <email> Policy owner's email address
|
|
69
129
|
--security-contact-email <email> Security reporting address
|
|
70
130
|
--timezone <iana-timezone> Program timezone, such as America/Chicago
|
|
131
|
+
--starter <profile> security (default) or foundation
|
|
71
132
|
--filegrc-version <version> Override resolved engine version
|
|
133
|
+
--filegrc-package <directory> Install an unpublished local filegrc package
|
|
134
|
+
--service-name <name> Complete service setup after creation
|
|
135
|
+
--boundary <description> Initial service boundary
|
|
136
|
+
--service-owner <person-id> Defaults to person-policy-owner
|
|
137
|
+
--criticality <level> low, medium, high, or critical
|
|
138
|
+
--classification <name> Initial data classification
|
|
139
|
+
--internet-exposed <bool> true or false
|
|
140
|
+
--program-goal <goal> none, readiness, type-1, or type-2
|
|
141
|
+
--setup-draft Save combined service setup as a draft
|
|
72
142
|
--no-install Write files and a preliminary lockfile only
|
|
73
143
|
--force Allow a non-empty target without overwriting files
|
|
74
144
|
--yes Use generic prompt defaults
|
|
@@ -80,3 +150,26 @@ async function printVersion() {
|
|
|
80
150
|
const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
81
151
|
console.log(packageJson.version);
|
|
82
152
|
}
|
|
153
|
+
|
|
154
|
+
async function readConfig(path) {
|
|
155
|
+
const source = path === "-"
|
|
156
|
+
? await readStdin()
|
|
157
|
+
: await readFile(resolve(String(path)), "utf8");
|
|
158
|
+
const parsed = JSON.parse(source);
|
|
159
|
+
if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") {
|
|
160
|
+
throw new Error("Creation config must be a JSON object.");
|
|
161
|
+
}
|
|
162
|
+
return parsed;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function readStdin() {
|
|
166
|
+
const chunks = [];
|
|
167
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
168
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function booleanOption(value, name) {
|
|
172
|
+
if (value === "true") return true;
|
|
173
|
+
if (value === "false") return false;
|
|
174
|
+
throw new Error(`--${name} must be true or false.`);
|
|
175
|
+
}
|
package/src/defaults.js
CHANGED
|
@@ -884,11 +884,11 @@ const obligations = [
|
|
|
884
884
|
}
|
|
885
885
|
];
|
|
886
886
|
|
|
887
|
-
export function baselineRecordPaths() {
|
|
888
|
-
return baselineRecordFiles("2000-01-01").map(({ path }) => path);
|
|
887
|
+
export function baselineRecordPaths(starter = "security") {
|
|
888
|
+
return baselineRecordFiles("2000-01-01", starter).map(({ path }) => path);
|
|
889
889
|
}
|
|
890
890
|
|
|
891
|
-
export function baselineRecordFiles(effectiveDate) {
|
|
891
|
+
export function baselineRecordFiles(effectiveDate, starter = "security") {
|
|
892
892
|
const framework = {
|
|
893
893
|
schemaVersion: 1,
|
|
894
894
|
id: FRAMEWORK_ID,
|
|
@@ -1006,20 +1006,25 @@ export function baselineRecordFiles(effectiveDate) {
|
|
|
1006
1006
|
policyIds: obligation.policyIds
|
|
1007
1007
|
}));
|
|
1008
1008
|
|
|
1009
|
+
const foundation = [
|
|
1010
|
+
recordFile("systems", programRepository),
|
|
1011
|
+
recordFile("teams", team)
|
|
1012
|
+
];
|
|
1013
|
+
if (starter === "foundation") return foundation;
|
|
1014
|
+
if (starter !== "security") throw new Error(`Unknown starter profile "${starter}".`);
|
|
1009
1015
|
return [
|
|
1010
1016
|
recordFile("frameworks", framework),
|
|
1011
1017
|
recordFile("frameworks", descriptionFramework),
|
|
1012
1018
|
...commonRequirements.map((record) => recordFile("requirements", record)),
|
|
1013
1019
|
...descriptionRequirements.map((record) => recordFile("requirements", record)),
|
|
1014
1020
|
...controlRecords.map((record) => recordFile("controls", record)),
|
|
1015
|
-
|
|
1016
|
-
recordFile("teams", team),
|
|
1021
|
+
...foundation,
|
|
1017
1022
|
...obligationRecords.map((record) => recordFile("obligations", record))
|
|
1018
1023
|
];
|
|
1019
1024
|
}
|
|
1020
1025
|
|
|
1021
|
-
export async function writeBaselineRecords(target, effectiveDate) {
|
|
1022
|
-
for (const { path: relativePath, record } of baselineRecordFiles(effectiveDate)) {
|
|
1026
|
+
export async function writeBaselineRecords(target, effectiveDate, starter = "security") {
|
|
1027
|
+
for (const { path: relativePath, record } of baselineRecordFiles(effectiveDate, starter)) {
|
|
1023
1028
|
const path = join(target, relativePath);
|
|
1024
1029
|
await mkdir(dirname(path), { recursive: true });
|
|
1025
1030
|
await writeFile(path, `${JSON.stringify(record, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
|
package/src/index.js
CHANGED
|
@@ -4,33 +4,43 @@ import { basename, dirname, extname, join, relative, resolve } from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
7
|
-
import { baselineRecordPaths, writeBaselineRecords } from "./defaults.js";
|
|
7
|
+
import { baselineRecordFiles, baselineRecordPaths, writeBaselineRecords } from "./defaults.js";
|
|
8
8
|
|
|
9
9
|
const execute = promisify(execFile);
|
|
10
10
|
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
11
11
|
const textExtensions = new Set(["", ".json", ".md", ".txt", ".yml", ".yaml", ".gitignore"]);
|
|
12
|
+
const STARTER_PROFILES = new Set(["foundation", "security"]);
|
|
13
|
+
const SECURITY_TEMPLATE_COLLECTIONS = new Set(["documents", "policies", "training"]);
|
|
12
14
|
|
|
13
15
|
export async function createFilegrc(options = {}) {
|
|
14
16
|
const parameterConfig = JSON.parse(await readFile(join(packageRoot, "template-parameters.json"), "utf8"));
|
|
15
17
|
const target = resolve(options.target ?? "filegrc-program");
|
|
18
|
+
const starter = normalizeStarterProfile(options.starter);
|
|
19
|
+
if (options.setup && options.install === false) {
|
|
20
|
+
throw new Error("Combined service setup requires installation. Remove --no-install or run filegrc setup after npm install.");
|
|
21
|
+
}
|
|
22
|
+
validateCombinedSetup(options.setup);
|
|
16
23
|
await assertWritableTarget(target, Boolean(options.force));
|
|
17
|
-
if (options.force) await assertNoTemplateCollisions(target);
|
|
24
|
+
if (options.force) await assertNoTemplateCollisions(target, starter);
|
|
18
25
|
|
|
19
26
|
const prompted = await resolvePromptValues(parameterConfig.parameters, options);
|
|
20
|
-
const
|
|
27
|
+
const engine = await resolveEngine(options);
|
|
28
|
+
const starterText = starterTemplateText(starter, prompted.company_name);
|
|
21
29
|
const values = {
|
|
22
30
|
...prompted,
|
|
23
31
|
effective_date: options.effectiveDate ?? new Date().toISOString().slice(0, 10),
|
|
24
32
|
project_name: normalizePackageName(basename(target)),
|
|
25
|
-
filegrc_version:
|
|
26
|
-
filegrc_version_range:
|
|
33
|
+
filegrc_version: engine.version,
|
|
34
|
+
filegrc_version_range: engine.dependency,
|
|
35
|
+
...starterText
|
|
27
36
|
};
|
|
28
37
|
|
|
29
38
|
await mkdir(target, { recursive: true });
|
|
30
|
-
await copyTemplate(target);
|
|
31
|
-
await renderTemplate(target, parameterConfig, values);
|
|
32
|
-
await writeBaselineRecords(target, values.effective_date);
|
|
33
|
-
|
|
39
|
+
await copyTemplate(target, starter);
|
|
40
|
+
await renderTemplate(target, parameterConfig, values, starter);
|
|
41
|
+
await writeBaselineRecords(target, values.effective_date, starter);
|
|
42
|
+
await applyStarterScope(target, starter, values.effective_date);
|
|
43
|
+
const initialResourceCounts = await summarizeResources(target);
|
|
34
44
|
|
|
35
45
|
const installed = options.install !== false;
|
|
36
46
|
if (installed) {
|
|
@@ -40,16 +50,36 @@ export async function createFilegrc(options = {}) {
|
|
|
40
50
|
}
|
|
41
51
|
const joinedExistingWorktree = await isInsideGitWorktree(target);
|
|
42
52
|
if (!joinedExistingWorktree) await run("git", ["init"], target);
|
|
53
|
+
const gitHead = await inspectGitHead(target);
|
|
54
|
+
const setup = options.setup ? await runCombinedSetup(target, options.setup) : null;
|
|
55
|
+
const resourceCounts = setup ? await summarizeResources(target) : initialResourceCounts;
|
|
43
56
|
return {
|
|
44
57
|
target,
|
|
45
58
|
values,
|
|
46
|
-
|
|
59
|
+
starter,
|
|
60
|
+
engineVersion: engine.version,
|
|
61
|
+
engineSource: engine.localPath ? "local" : "registry",
|
|
62
|
+
enginePackage: engine.localPath || null,
|
|
63
|
+
dependency: engine.dependency,
|
|
64
|
+
stages: starterStages(starter, initialResourceCounts),
|
|
47
65
|
resourceCounts,
|
|
66
|
+
setup,
|
|
48
67
|
install: installed ? "installed" : "skipped",
|
|
49
|
-
gitMode: joinedExistingWorktree ? "existing-worktree" : "initialized"
|
|
68
|
+
gitMode: joinedExistingWorktree ? "existing-worktree" : "initialized",
|
|
69
|
+
gitBranch: gitHead.branch,
|
|
70
|
+
gitDetached: gitHead.detached
|
|
50
71
|
};
|
|
51
72
|
}
|
|
52
73
|
|
|
74
|
+
export function normalizeStarterProfile(value = "security") {
|
|
75
|
+
const profile = String(value || "security").trim().toLowerCase();
|
|
76
|
+
const normalized = profile === "empty" ? "foundation" : profile === "soc2-security" ? "security" : profile;
|
|
77
|
+
if (!STARTER_PROFILES.has(normalized)) {
|
|
78
|
+
throw new Error(`Starter profile must be one of ${[...STARTER_PROFILES].join(", ")}.`);
|
|
79
|
+
}
|
|
80
|
+
return normalized;
|
|
81
|
+
}
|
|
82
|
+
|
|
53
83
|
export async function resolveFilegrcVersion(explicitVersion) {
|
|
54
84
|
if (explicitVersion) return cleanVersion(explicitVersion);
|
|
55
85
|
try {
|
|
@@ -65,6 +95,28 @@ export async function resolveFilegrcVersion(explicitVersion) {
|
|
|
65
95
|
}
|
|
66
96
|
}
|
|
67
97
|
|
|
98
|
+
async function resolveEngine(options) {
|
|
99
|
+
if (options.filegrcVersion && options.filegrcPackage) {
|
|
100
|
+
throw new Error("Use either filegrcVersion or filegrcPackage, not both.");
|
|
101
|
+
}
|
|
102
|
+
if (!options.filegrcPackage) {
|
|
103
|
+
const version = await resolveFilegrcVersion(options.filegrcVersion);
|
|
104
|
+
return { version, dependency: `^${version}`, localPath: null };
|
|
105
|
+
}
|
|
106
|
+
const localPath = resolve(String(options.filegrcPackage));
|
|
107
|
+
let packageJson;
|
|
108
|
+
try {
|
|
109
|
+
packageJson = JSON.parse(await readFile(join(localPath, "package.json"), "utf8"));
|
|
110
|
+
} catch (error) {
|
|
111
|
+
throw new Error(`Could not read a local filegrc package at ${localPath}: ${error.message}`);
|
|
112
|
+
}
|
|
113
|
+
if (packageJson.name !== "filegrc") {
|
|
114
|
+
throw new Error(`Local package at ${localPath} is named "${packageJson.name || ""}", expected "filegrc".`);
|
|
115
|
+
}
|
|
116
|
+
const version = cleanVersion(packageJson.version);
|
|
117
|
+
return { version, dependency: `file:${localPath.replaceAll("\\", "/")}`, localPath };
|
|
118
|
+
}
|
|
119
|
+
|
|
68
120
|
async function resolvePromptValues(parameters, options) {
|
|
69
121
|
const mapped = {
|
|
70
122
|
company_name: options.companyName,
|
|
@@ -161,9 +213,9 @@ async function assertWritableTarget(target, force) {
|
|
|
161
213
|
}
|
|
162
214
|
}
|
|
163
215
|
|
|
164
|
-
async function assertNoTemplateCollisions(target) {
|
|
216
|
+
async function assertNoTemplateCollisions(target, starter) {
|
|
165
217
|
const collisions = [];
|
|
166
|
-
for (const destinationPath of [...await templateDestinationPaths(), ...baselineRecordPaths()]) {
|
|
218
|
+
for (const destinationPath of [...await templateDestinationPaths(starter), ...baselineRecordPaths(starter)]) {
|
|
167
219
|
await assertNoSymlinkComponents(target, destinationPath);
|
|
168
220
|
try {
|
|
169
221
|
await lstat(join(target, destinationPath));
|
|
@@ -192,20 +244,22 @@ async function assertNoSymlinkComponents(target, relativePath) {
|
|
|
192
244
|
}
|
|
193
245
|
}
|
|
194
246
|
|
|
195
|
-
async function renderTemplate(target, parameterConfig, values) {
|
|
247
|
+
async function renderTemplate(target, parameterConfig, values, starter) {
|
|
196
248
|
const declared = new Set([
|
|
197
249
|
...parameterConfig.parameters.map(({ key }) => key),
|
|
198
250
|
...parameterConfig.generated.map(({ key }) => key)
|
|
199
251
|
]);
|
|
200
|
-
const files = (await templateDestinationPaths()).map((path) => join(target, path));
|
|
252
|
+
const files = (await templateDestinationPaths(starter)).map((path) => join(target, path));
|
|
201
253
|
for (const path of files) {
|
|
202
254
|
if (!textExtensions.has(extname(path)) && basename(path) !== ".gitignore") continue;
|
|
203
255
|
let source = await readFile(path, "utf8");
|
|
204
256
|
const jsonFile = extname(path) === ".json";
|
|
205
|
-
source = source.replace(/\{\{([a-z0-9_]+)\}\}
|
|
257
|
+
source = source.replace(/\{\{([a-z0-9_]+)\}\}(\.)?/g, (match, token, sentencePeriod = "") => {
|
|
206
258
|
if (!declared.has(token)) throw new Error(`Unknown template token "{{${token}}}" in ${path}`);
|
|
207
259
|
if (values[token] === undefined) throw new Error(`No value resolved for template token "{{${token}}}"`);
|
|
208
|
-
|
|
260
|
+
const value = jsonFile ? jsonStringContents(values[token]) : String(values[token]);
|
|
261
|
+
const punctuation = !jsonFile && sentencePeriod && /[.!?]$/u.test(value) ? "" : sentencePeriod;
|
|
262
|
+
return value + punctuation;
|
|
209
263
|
});
|
|
210
264
|
const unresolved = /\{\{([a-z0-9_]+)\}\}/.exec(source);
|
|
211
265
|
if (unresolved) throw new Error(`Unresolved template token "{{${unresolved[1]}}}" in ${path}`);
|
|
@@ -213,20 +267,22 @@ async function renderTemplate(target, parameterConfig, values) {
|
|
|
213
267
|
}
|
|
214
268
|
}
|
|
215
269
|
|
|
216
|
-
async function templateDestinationPaths() {
|
|
270
|
+
async function templateDestinationPaths(starter = "security") {
|
|
217
271
|
const template = join(packageRoot, "template");
|
|
218
272
|
return (await collectFiles(template)).flatMap((source) => {
|
|
219
273
|
const templatePath = relative(template, source);
|
|
274
|
+
if (!includeTemplatePath(templatePath, starter)) return [];
|
|
220
275
|
if (templatePath === "README.md") return [];
|
|
221
276
|
if (templatePath === "WORKSPACE.md") return ["README.md"];
|
|
222
277
|
return [templatePath === "gitignore" ? ".gitignore" : templatePath];
|
|
223
278
|
});
|
|
224
279
|
}
|
|
225
280
|
|
|
226
|
-
async function copyTemplate(target) {
|
|
281
|
+
async function copyTemplate(target, starter = "security") {
|
|
227
282
|
const template = join(packageRoot, "template");
|
|
228
283
|
for (const source of await collectFiles(template)) {
|
|
229
284
|
const templatePath = relative(template, source);
|
|
285
|
+
if (!includeTemplatePath(templatePath, starter)) continue;
|
|
230
286
|
if (templatePath === "README.md") continue;
|
|
231
287
|
const destinationPath = templatePath === "WORKSPACE.md"
|
|
232
288
|
? "README.md"
|
|
@@ -242,6 +298,13 @@ async function copyTemplate(target) {
|
|
|
242
298
|
}
|
|
243
299
|
}
|
|
244
300
|
|
|
301
|
+
function includeTemplatePath(templatePath, starter) {
|
|
302
|
+
if (starter === "security") return true;
|
|
303
|
+
const segments = templatePath.split(/[\\/]/);
|
|
304
|
+
if (segments[0] !== "data" || !SECURITY_TEMPLATE_COLLECTIONS.has(segments[1])) return true;
|
|
305
|
+
return segments.at(-1) === "AGENTS.md";
|
|
306
|
+
}
|
|
307
|
+
|
|
245
308
|
async function collectFiles(directory) {
|
|
246
309
|
const result = [];
|
|
247
310
|
for (const item of await readdir(directory, { withFileTypes: true })) {
|
|
@@ -265,16 +328,137 @@ async function summarizeResources(target) {
|
|
|
265
328
|
return { total, byType: counts };
|
|
266
329
|
}
|
|
267
330
|
|
|
331
|
+
async function applyStarterScope(target, starter, effectiveDate) {
|
|
332
|
+
if (starter !== "security") return;
|
|
333
|
+
const records = baselineRecordFiles(effectiveDate, starter).map(({ record }) => record);
|
|
334
|
+
const workspacePath = join(target, "data", "workspace.json");
|
|
335
|
+
const workspace = JSON.parse(await readFile(workspacePath, "utf8"));
|
|
336
|
+
const ids = (type) => records.filter((record) => record.type === type).map((record) => record.id);
|
|
337
|
+
await writeFile(workspacePath, `${JSON.stringify({
|
|
338
|
+
...workspace,
|
|
339
|
+
frameworkIds: ids("framework"),
|
|
340
|
+
requirementIds: ids("requirement"),
|
|
341
|
+
controlIds: ids("control")
|
|
342
|
+
}, null, 2)}\n`, "utf8");
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function starterStages(starter, counts) {
|
|
346
|
+
const foundationTypes = new Set(["workspace", "renderer-settings", "person", "team", "system"]);
|
|
347
|
+
const foundation = Object.entries(counts.byType)
|
|
348
|
+
.filter(([type]) => foundationTypes.has(type))
|
|
349
|
+
.reduce((total, [, count]) => total + count, 0);
|
|
350
|
+
return [
|
|
351
|
+
{ id: "foundation", status: "created", records: foundation },
|
|
352
|
+
{
|
|
353
|
+
id: "soc2-security",
|
|
354
|
+
status: starter === "security" ? "created" : "skipped",
|
|
355
|
+
records: starter === "security" ? counts.total - foundation : 0
|
|
356
|
+
}
|
|
357
|
+
];
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function starterTemplateText(starter, companyName) {
|
|
361
|
+
if (starter === "foundation") {
|
|
362
|
+
return {
|
|
363
|
+
program_title: `${companyName} GRC Program`,
|
|
364
|
+
program_description: "Governance, risk, and compliance workspace without a preselected framework.",
|
|
365
|
+
program_summary: `This private workspace holds ${companyName}'s governance, risk, compliance, and audit-evidence records. JSON under \`data/\` stores structured records, Markdown stores long-form work, and Git records reviewed changes.`,
|
|
366
|
+
agent_title: "filegrc Workspace Instructions",
|
|
367
|
+
agent_purpose: `This repository is ${companyName}’s foundation filegrc workspace. Engineers and agents maintain the source records under \`data/\`. The \`filegrc\` package validates, searches, edits, and renders those files. No framework or assurance program has been selected yet.`,
|
|
368
|
+
starter_baseline: `## Foundation baseline
|
|
369
|
+
|
|
370
|
+
The generated workspace starts with five structural records:
|
|
371
|
+
|
|
372
|
+
- Workspace and renderer settings
|
|
373
|
+
- The initial active owner
|
|
374
|
+
- An inactive security and risk oversight team that still needs an independent chair
|
|
375
|
+
- The filegrc Git repository as a governance system of record
|
|
376
|
+
- A default 5x5 risk method and Public, Internal, Confidential, and Restricted data classifications
|
|
377
|
+
|
|
378
|
+
This profile does not include framework requirements, policies, governed documents, training, controls, obligations, or audit-management templates. Add and review those records for the selected framework before treating Program Readiness or Audit Readiness as meaningful. Do not infer that an absent control, policy, or schedule is unnecessary.`,
|
|
379
|
+
audit_preparation_guidance: "The foundation profile does not include the local SOC 2 management-document templates used by `prepare-audit`. Add reviewed templates and program scope before initializing audit work. Audit preparation must not invent missing policy, control, or evidence facts.",
|
|
380
|
+
starter_setup: `## Start the program
|
|
381
|
+
|
|
382
|
+
This foundation profile contains the workspace, initial owner, oversight team, renderer settings, and filegrc system of record. It does not select a framework or create proposed policies, controls, obligations, or evidence.
|
|
383
|
+
|
|
384
|
+
1. Run \`npx filegrc setup\` for guided service and goal setup, or use browser onboarding.
|
|
385
|
+
2. Use \`npx filegrc guide --json\` before creating framework requirements, policies, controls, obligations, and evidence sources.
|
|
386
|
+
3. Run \`npx filegrc validate\`, review the Git diff, and commit each reviewed program layer.`
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
program_title: `${companyName} SOC 2 Program`,
|
|
391
|
+
program_description: "SOC 2 Security program based on the AICPA Trust Services Criteria.",
|
|
392
|
+
program_summary: `This private workspace holds ${companyName}'s SOC 2 program records and audit evidence. JSON under \`data/\` stores structured records, Markdown stores long-form work, and Git records reviewed changes.`,
|
|
393
|
+
agent_title: "filegrc SOC 2 Workspace Instructions",
|
|
394
|
+
agent_purpose: `This repository is ${companyName}’s filegrc workspace for its SOC 2 program. Engineers and agents maintain the source records under \`data/\`. The \`filegrc\` package validates, searches, edits, and renders those files.`,
|
|
395
|
+
starter_baseline: `## Starter baseline
|
|
396
|
+
|
|
397
|
+
The generated workspace starts with the SOC 2 Security category:
|
|
398
|
+
|
|
399
|
+
- Active framework records for the 2017 Trust Services Criteria with revised points of focus (2022) and the 2018 SOC 2 Description Criteria with revised implementation guidance (2022)
|
|
400
|
+
- The 33 Common Criteria reference IDs from CC1.1 through CC9.2, without the licensed criteria text
|
|
401
|
+
- The nine Description Criteria reference IDs from DC1 through DC9, without the licensed criteria text
|
|
402
|
+
- Planned controls mapped to those references and the included policies
|
|
403
|
+
- A security and risk oversight team chaired by an independent reviewer who may be internal or external
|
|
404
|
+
- Recurring obligations for the reviews, scans, tests, training, and meetings required by the included policies
|
|
405
|
+
- A default 5x5 risk method and Public, Internal, Confidential, and Restricted data classifications
|
|
406
|
+
|
|
407
|
+
Treat every planned control as a proposal until its owner, actual procedure in Record Markdown, system scope, cadence, authoritative evidence sources, implementation date, and mappings match actual practice. For a control linked to filegrc obligations, every non-retired Work Queue schedule must be enabled and its governing policies effective. Marking the control implemented starts eligible schedules. Do not mark a control implemented because a policy describes it. Add Availability, Processing Integrity, Confidentiality, or Privacy criteria only when they are in scope.
|
|
408
|
+
|
|
409
|
+
The recurring obligations mirror the fixed cadences in the starter policies. They remain proposals until every governing policy is active and effective and, when they name controls, at least one linked control is implemented. Update the policy, control, and obligation together when an approved cadence changes. Create separate completion records, such as meetings, reviews, scans, tests, exercises, and attestations, for each period.`,
|
|
410
|
+
audit_preparation_guidance: "Preparation creates a separate system description, management assertion, and management representation document for the engagement from the local starter templates. Type 2 preparation also creates a period completeness statement and one `audit-population` record for each standard population. It is safe to run again and does not approve documents, mark controls implemented, or create evidence. Do not reuse one completed management document across engagements.",
|
|
411
|
+
starter_setup: `## Finish initial setup
|
|
412
|
+
|
|
413
|
+
The starter policies, controls, and obligations are proposals. They do not state that ${companyName} operates the described controls.
|
|
414
|
+
|
|
415
|
+
1. Run \`npx filegrc setup\` for guided service and goal setup, or use browser onboarding. Then finish Step 1 by adding the real reviewers and operators, finishing the oversight team, and confirming applicable criteria, commitments, material vendors, and in-scope systems.
|
|
416
|
+
2. Review the starter policies, appoint a reviewer who is separate from the policy owner, and activate only the policies that match current practice. The reviewer will usually be another person in the organization, but may be external.
|
|
417
|
+
3. Review the starter control set, implement each applicable control with its actual procedure, scope, cadence, evidence sources, and implementation date, and confirm any linked Work Queue schedules are enabled. Marking a control implemented starts eligible schedules. Then record any complementary customer or subservice controls.
|
|
418
|
+
4. Preview External Evidence drafts with \`npx filegrc evidence-test-drafts --preview --json\`. Create them only after confirming applicable controls and authoritative source systems.
|
|
419
|
+
5. Run \`npx filegrc program-readiness --require-ready\`, record the management candidate period start when reliable evidence collection begins, maintain risk assessments and risks, update controls when needed, use Work Queue for scheduled work, and trigger Policy Events when changes create required actions.
|
|
420
|
+
6. Engage a CPA firm, record the separate firm-agreed period in an audit record, review filegrc Evidence and External Evidence, and prepare fieldwork.`
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function validateCombinedSetup(setup) {
|
|
425
|
+
if (!setup) return;
|
|
426
|
+
if (Array.isArray(setup) || typeof setup !== "object") throw new Error("setup must be a JSON object.");
|
|
427
|
+
const required = ["serviceName", "boundary", "criticality", "dataClassification", "internetExposed"];
|
|
428
|
+
const missing = required.filter((name) => setup[name] === undefined || setup[name] === "");
|
|
429
|
+
if (missing.length) throw new Error(`Combined setup is missing: ${missing.join(", ")}.`);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async function runCombinedSetup(target, input) {
|
|
433
|
+
const setup = { programGoal: "none", ownerId: "person-policy-owner", ...input };
|
|
434
|
+
const args = [
|
|
435
|
+
join(target, "node_modules", "filegrc", "bin", "filegrc.js"),
|
|
436
|
+
"setup",
|
|
437
|
+
"--service-name", String(setup.serviceName),
|
|
438
|
+
"--boundary", String(setup.boundary),
|
|
439
|
+
"--owner", String(setup.ownerId),
|
|
440
|
+
"--criticality", String(setup.criticality),
|
|
441
|
+
"--classification", String(setup.dataClassification),
|
|
442
|
+
"--internet-exposed", String(setup.internetExposed),
|
|
443
|
+
"--program-goal", String(setup.programGoal),
|
|
444
|
+
"--summary",
|
|
445
|
+
"--json"
|
|
446
|
+
];
|
|
447
|
+
if (setup.draft === true) args.push("--draft");
|
|
448
|
+
const result = await run(process.execPath, args, target);
|
|
449
|
+
return JSON.parse(result.stdout);
|
|
450
|
+
}
|
|
451
|
+
|
|
268
452
|
async function writeMinimalLockfile(target, name, versionRange) {
|
|
269
453
|
const lock = {
|
|
270
454
|
name,
|
|
271
|
-
version: "0.3.
|
|
455
|
+
version: "0.3.2",
|
|
272
456
|
lockfileVersion: 3,
|
|
273
457
|
requires: true,
|
|
274
458
|
packages: {
|
|
275
459
|
"": {
|
|
276
460
|
name,
|
|
277
|
-
version: "0.3.
|
|
461
|
+
version: "0.3.2",
|
|
278
462
|
dependencies: { filegrc: versionRange }
|
|
279
463
|
}
|
|
280
464
|
}
|
|
@@ -291,6 +475,20 @@ async function isInsideGitWorktree(target) {
|
|
|
291
475
|
}
|
|
292
476
|
}
|
|
293
477
|
|
|
478
|
+
async function inspectGitHead(target) {
|
|
479
|
+
try {
|
|
480
|
+
const { stdout } = await execute("git", ["symbolic-ref", "--quiet", "--short", "HEAD"], { cwd: target });
|
|
481
|
+
return { branch: stdout.trim() || null, detached: false };
|
|
482
|
+
} catch {
|
|
483
|
+
try {
|
|
484
|
+
await execute("git", ["rev-parse", "--verify", "HEAD"], { cwd: target });
|
|
485
|
+
return { branch: null, detached: true };
|
|
486
|
+
} catch {
|
|
487
|
+
return { branch: null, detached: false };
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
294
492
|
async function run(command, args, cwd) {
|
|
295
493
|
try {
|
|
296
494
|
return await execute(command, args, { cwd, maxBuffer: 10_000_000 });
|
package/template/AGENTS.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# {{agent_title}}
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
{{agent_purpose}}
|
|
6
6
|
|
|
7
7
|
Using this repository does not establish compliance by itself. Records must match actual practice and evidence must prove that controls operated during the audit period.
|
|
8
8
|
|
|
@@ -12,13 +12,13 @@ Do not guess a resource type, field name, enum value, relationship, or file path
|
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
14
|
npx filegrc guide --json
|
|
15
|
-
npx filegrc program-path --json
|
|
15
|
+
npx filegrc program-path --next --json
|
|
16
16
|
npx filegrc guide risk-assessment --json
|
|
17
17
|
npx filegrc list person --json
|
|
18
18
|
npx filegrc program-readiness --summary --json
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
`program-path` gives agents the
|
|
21
|
+
`program-path --next --json` gives agents the current step and first action. Use `--summary` for all six step statuses or `--current` for the current step’s full renderer Instructions, Use, Policy Basis, commands, and next actions. The general guide lists every supported action and record type. A type guide repeats that page guidance and adds timing, required and conditional fields, current relationship candidates, JSON location, and Markdown slots.
|
|
22
22
|
|
|
23
23
|
For a new record, generate a mutation envelope:
|
|
24
24
|
|
|
@@ -95,25 +95,11 @@ Headless agents get the same protection by exporting an edit payload with `fileg
|
|
|
95
95
|
|
|
96
96
|
`data/renderer.json` stores committed renderer preferences. New workspaces set `showOnboarding` to `true`. Completing or skipping onboarding sets it to `false`; the app does not commit that change.
|
|
97
97
|
|
|
98
|
-
Onboarding explains the file and Git workflow, the program path, policy obligations, and Policy Events before covering report types and the final audit stage. It then collects the initial service boundary, owner, business criticality, highest data classification, internet exposure, and optional program goal. It creates or updates
|
|
98
|
+
Onboarding explains the file and Git workflow, the program path, policy obligations, and Policy Events before covering report types and the final audit stage. It then collects the initial service boundary, owner, business criticality, highest data classification, internet exposure, and optional program goal. It creates or updates one `system` record and stores that selected system and the management goal on `workspace`. It does not select framework records, link controls to the service, or create evidence. Selecting Type 1 or Type 2 does not create an audit engagement. Completing onboarding opens the Step 1 overview so the user can add the real reviewers and operators, finish the oversight team, and confirm the criteria, commitments, vendors, and systems before approving policies.
|
|
99
99
|
|
|
100
100
|
The renderer is optional. Agents may set `showOnboarding` to `false` and maintain all records headlessly. Restart onboarding from Repository when useful. Read-only builds never run it.
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
The generated workspace starts with the SOC 2 Security category:
|
|
105
|
-
|
|
106
|
-
- Active framework records for the 2017 Trust Services Criteria with revised points of focus (2022) and the 2018 SOC 2 Description Criteria with revised implementation guidance (2022)
|
|
107
|
-
- The 33 Common Criteria reference IDs from CC1.1 through CC9.2, without the licensed criteria text
|
|
108
|
-
- The nine Description Criteria reference IDs from DC1 through DC9, without the licensed criteria text
|
|
109
|
-
- Planned controls mapped to those references and the included policies
|
|
110
|
-
- A security and risk oversight team chaired by an independent reviewer who may be internal or external
|
|
111
|
-
- Recurring obligations for the reviews, scans, tests, training, and meetings required by the included policies
|
|
112
|
-
- A default 5x5 risk method and Public, Internal, Confidential, and Restricted data classifications
|
|
113
|
-
|
|
114
|
-
Treat every planned control as a proposal until its owner, actual procedure in Record Markdown, system scope, cadence, authoritative evidence sources, implementation date, and mappings match actual practice. For a control linked to filegrc obligations, every non-retired Work Queue schedule must be enabled and its governing policies effective. Marking the control implemented starts eligible schedules. Do not mark a control implemented because a policy describes it. Add Availability, Processing Integrity, Confidentiality, or Privacy criteria only when they are in scope.
|
|
115
|
-
|
|
116
|
-
The recurring obligations mirror the fixed cadences in the starter policies. They remain proposals until every governing policy is active and effective and, when they name controls, at least one linked control is implemented. Update the policy, control, and obligation together when an approved cadence changes. Create separate completion records, such as meetings, reviews, scans, tests, exercises, and attestations, for each period.
|
|
102
|
+
{{starter_baseline}}
|
|
117
103
|
|
|
118
104
|
## Work Queue and Policy Events
|
|
119
105
|
|
|
@@ -180,7 +166,7 @@ The Evidence Ready gate requires:
|
|
|
180
166
|
4. Active authoritative systems with evidence source roles, access owners, and repeatable extraction instructions in Record Markdown.
|
|
181
167
|
5. A verified `test-export` or `test-capture` evidence record for each selected control family that relies on evidence from outside filegrc.
|
|
182
168
|
|
|
183
|
-
|
|
169
|
+
Onboarding does not create External Evidence records. After confirming the applicable controls and authoritative source Systems, run `npx filegrc evidence-test-drafts --preview --json` and review the proposed collection tests. Then run `npx filegrc evidence-test-drafts` to create the missing drafts. filegrc-managed records, such as risk assessments, meetings, vendor reviews, attestations, vulnerability scans, penetration tests, backup tests, exercises, exceptions, and findings, do not need a separate collection test. Put any fixed external artifact in an External Evidence record and link it from the operating record. For each created draft, choose its authoritative source System, attach or reference the real result, record its collector and classification, then have another person verify it.
|
|
184
170
|
|
|
185
171
|
When the gate passes, set `workspace.candidatePeriodStart` to the date reliable evidence collection begins. Do not backdate it. `candidatePeriodStart` and `candidatePeriodEnd` express management’s target. They do not establish the final report period.
|
|
186
172
|
|
|
@@ -200,7 +186,7 @@ npx filegrc audit-readiness audit-2026-type-2 --require-ready --json
|
|
|
200
186
|
|
|
201
187
|
The audit record’s `typeOneAsOf`, `periodStart`, and `periodEnd` are the dates agreed with the CPA firm. Keep the workspace candidate dates even when the formal period differs.
|
|
202
188
|
|
|
203
|
-
|
|
189
|
+
{{audit_preparation_guidance}}
|
|
204
190
|
|
|
205
191
|
Review both evidence paths against the exact firm-agreed date or period:
|
|
206
192
|
|
package/template/README.md
CHANGED
|
@@ -4,28 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
Run a SOC 2 program as files in Git.
|
|
6
6
|
|
|
7
|
-
filegrc gives
|
|
7
|
+
filegrc gives founder-led engineering teams one place to adopt policies, implement controls, run recurring work, collect evidence, and prepare an audit.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
## Why it exists
|
|
12
|
-
|
|
13
|
-
SOC 2 work tends to scatter across documents, calendars, tickets, screenshots, and the auditor’s request list. That makes it hard to answer basic questions: What is due? Which policy requires it? What changed during the audit period? Is the evidence complete?
|
|
14
|
-
|
|
15
|
-
filegrc keeps that work connected:
|
|
16
|
-
|
|
17
|
-
- A starter Security program links criteria references, policies, planned controls, owners, and schedules.
|
|
18
|
-
- Work Queue turns policy timing into upcoming, due, and overdue work.
|
|
19
|
-
- Policy Events add the required hiring, departure, vendor, incident, and change tasks to the Work Queue.
|
|
20
|
-
- Program Readiness says whether management can begin a candidate Type 2 evidence period without an audit record.
|
|
21
|
-
- Audit Readiness starts later with the CPA engagement, formal period, fieldwork documents, populations, and evidence delivery.
|
|
22
|
-
- The packet builder produces a scoped, indexed delivery with source files, attachments, history, and checksums.
|
|
23
|
-
|
|
24
|
-
The starter content is a proposal, not a claim of compliance. Review every policy and planned control against how your company actually operates before approving it.
|
|
25
|
-
|
|
26
|
-
## Start a workspace
|
|
27
|
-
|
|
28
|
-
You need Node.js 20 or newer and Git.
|
|
9
|
+
It is open source, MIT licensed, and runs locally.
|
|
29
10
|
|
|
30
11
|
```sh
|
|
31
12
|
npx create-filegrc@latest company-grc
|
|
@@ -34,94 +15,64 @@ npm run validate
|
|
|
34
15
|
npm run serve
|
|
35
16
|
```
|
|
36
17
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Open the printed local URL. You can commit locally from Repository without configuring a remote. Add a remote when the team is ready to share the workspace, then the browser can pull with rebase and push reviewed commits.
|
|
40
|
-
|
|
41
|
-
The creation summary reports the resolved engine version, program timezone, starter record counts, install result, and whether the target joined an existing Git worktree. Generated workspaces receive an organization-specific README with their engine version, validation commands, and remaining setup work.
|
|
18
|
+
Requires Node.js 20 or newer and Git.
|
|
42
19
|
|
|
43
20
|
## How it works
|
|
44
21
|
|
|
45
|
-
|
|
46
|
-
2. Review and activate the policies with a separate management reviewer, who is usually internal and may be external.
|
|
47
|
-
3. Tailor the starter controls, add each owner, actual procedure, scope, cadence, evidence source, and implementation date, and confirm any linked Work Queue schedules are enabled. Marking a control implemented starts eligible schedules. Then record any complementary customer or subservice controls.
|
|
48
|
-
4. Open each generated External Evidence draft, choose its authoritative source System, collect the named artifact, and have another person verify it.
|
|
49
|
-
5. Start the management candidate period, maintain risk assessments and risks, update controls when needed, work the filegrc queue, and preserve dated evidence.
|
|
50
|
-
6. Engage a CPA firm, record the separate firm-agreed period, review filegrc Evidence and External Evidence, prepare fieldwork, and generate the evidence packet.
|
|
51
|
-
|
|
52
|
-
Long-form policies, procedures, plans, minutes, training, assertions, and audit responses are Markdown companions beside their JSON records. Screenshots, signed acknowledgements, reports, and fixed exports are attachments linked through evidence records.
|
|
22
|
+
The repository is the program. There is no separate application database.
|
|
53
23
|
|
|
54
|
-
|
|
24
|
+
- **JSON** holds records that filegrc validates, filters, and connects.
|
|
25
|
+
- **Markdown** holds policies, procedures, plans, minutes, and narratives.
|
|
26
|
+
- **Git** supplies authors, timestamps, revisions, diffs, and commit messages.
|
|
55
27
|
|
|
56
|
-
|
|
28
|
+
Use the same source through the local web app, a text editor, the CLI, or CI. Browser and CLI actions call the same rules, so engineers and agents see the same validation and readiness results.
|
|
57
29
|
|
|
58
|
-
|
|
30
|
+
## One path from setup to audit
|
|
59
31
|
|
|
60
32
|

|
|
61
33
|
|
|
62
|
-
|
|
34
|
+
1. **Define scope.** Confirm owners, criteria, commitments, vendors, and in-scope systems.
|
|
35
|
+
2. **Approve policies.** Tailor the proposals and record separate owners and reviewers.
|
|
36
|
+
3. **Implement controls.** Add the real procedure, scope, cadence, and evidence source.
|
|
37
|
+
4. **Test evidence collection.** Collect and verify evidence from each authoritative system.
|
|
38
|
+
5. **Operate the program.** Work the queue, trigger Policy Events, maintain risks, and preserve dated evidence.
|
|
39
|
+
6. **Audit.** Record the CPA engagement and agreed period, support fieldwork, and build the packet.
|
|
63
40
|
|
|
64
|
-
|
|
41
|
+
The Program Overview shows what is done, what is blocked, and what to do next.
|
|
65
42
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
```sh
|
|
69
|
-
npx filegrc guide risk-assessment --json
|
|
70
|
-
npx filegrc program-path --json
|
|
71
|
-
npx filegrc scaffold risk-assessment --title "2026 Annual Risk Assessment"
|
|
72
|
-
npx filegrc list risk --json
|
|
73
|
-
npx filegrc obligations --json
|
|
74
|
-
npx filegrc program-readiness --summary --json
|
|
75
|
-
npx filegrc complete obligation-id completion-record.json
|
|
76
|
-
npx filegrc trigger person-started --occurred-on 2026-07-25 --subject person-id
|
|
77
|
-
npx filegrc complete-action action-item-id completion-record.json --completed-on 2026-07-25
|
|
78
|
-
npx filegrc complete-event obligation-event-id --completed-on 2026-07-25
|
|
79
|
-
npx filegrc search "access review"
|
|
80
|
-
```
|
|
43
|
+
## The routine work stays connected
|
|
81
44
|
|
|
82
|
-
|
|
45
|
+
- **Work Queue** turns policy schedules and follow-up into upcoming, due, and overdue work.
|
|
46
|
+
- **Policy Events** create the right tasks for hiring, departures, incidents, vendor changes, and other events.
|
|
47
|
+
- **Program Readiness** checks whether management can begin a reliable evidence period.
|
|
48
|
+
- **Audit Readiness** checks the engagement, period, documents, evidence, and Type 2 populations.
|
|
49
|
+
- **Evidence packets** collect the scoped records, attachments, history, indexes, and checksums for delivery.
|
|
83
50
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
Program Readiness works without an audit ID or CPA firm:
|
|
87
|
-
|
|
88
|
-
```sh
|
|
89
|
-
npx filegrc program-readiness --json
|
|
90
|
-
npx filegrc program-readiness --require-ready
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
The Evidence Ready gate requires defined scope, effective policies, implemented controls, configured authoritative systems, and verified collection for external evidence that does not already have a dedicated Step 5 record. Put scan reports, backup output, and other fixed artifacts in External Evidence records, then link them from the applicable Step 5 operating records. Starter obligations remain enabled proposals until their governing policies are effective and at least one linked control is implemented. A filegrc-managed control cannot be implemented while one of its linked Work Queue schedules is paused or waiting for policy approval.
|
|
94
|
-
|
|
95
|
-
When the gate passes, record `candidatePeriodStart` on the workspace on the date reliable collection begins. This is management’s candidate Type 2 period. Do not backdate it. The later audit record keeps the separate period agreed with the CPA firm.
|
|
51
|
+

|
|
96
52
|
|
|
97
|
-
|
|
53
|
+
Starter records connect policies, controls, owners, systems, evidence, and schedules. They are proposals, so review them against how your company actually works before approval.
|
|
98
54
|
|
|
99
|
-
|
|
55
|
+
## Built for engineers and agents
|
|
100
56
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
For a Type 2 audit, reconcile each complete period population to its authoritative system after the period closes. A zero-item population still needs its source export and query. filegrc Evidence consists of dated operating records and their Markdown and Git history. External Evidence consists of verified exports, reports, screenshots, signed files, and approved external references. The packet compiles both paths with the selected records, attachments, indexes, historical versions, and SHA-256 checksums.
|
|
57
|
+
The browser is helpful, but it is not required. An agent can discover the model, inspect valid relationships, create records, complete scheduled work, trigger events, and check the result from the CLI.
|
|
104
58
|
|
|
105
59
|
```sh
|
|
106
|
-
npx filegrc
|
|
60
|
+
npx filegrc program-path --next --json
|
|
61
|
+
npx filegrc guide risk-assessment --json
|
|
62
|
+
npx filegrc obligations --json
|
|
63
|
+
npx filegrc program-readiness --summary --json
|
|
107
64
|
npx filegrc audit-readiness audit-id --json
|
|
108
65
|
npx filegrc evidence-packet --audit audit-id
|
|
109
66
|
```
|
|
110
67
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
Early CPA engagement remains available when a customer deadline, unusual scope, or other timing risk needs input before the program reaches Evidence Ready. It is optional, not the default first action.
|
|
114
|
-
|
|
115
|
-
## What belongs elsewhere
|
|
116
|
-
|
|
117
|
-
filegrc does not replace workforce, identity, source-control, deployment, infrastructure, monitoring, endpoint, backup, vulnerability, training, signature, procurement, contract, or vendor-risk systems.
|
|
68
|
+
Read `AGENTS.md` and `data/AGENTS.md` inside a generated workspace for the full headless workflow.
|
|
118
69
|
|
|
119
|
-
|
|
70
|
+
## Clear boundaries
|
|
120
71
|
|
|
121
|
-
|
|
72
|
+
filegrc manages GRC records and audit evidence. Your workforce, identity, source control, infrastructure, monitoring, endpoint, backup, training, signature, procurement, and vendor systems still operate the controls and produce source evidence.
|
|
122
73
|
|
|
123
|
-
|
|
74
|
+
The independent CPA firm still selects samples, tests controls, evaluates exceptions, decides whether evidence is sufficient, and issues the SOC 2 report.
|
|
124
75
|
|
|
125
|
-
|
|
76
|
+
Do not put secrets or personal data that may need erasure into Git. The editable local server has no authentication and binds to loopback by default.
|
|
126
77
|
|
|
127
|
-
|
|
78
|
+
Learn more at [filegrc.com](https://filegrc.com) or [view the source on GitHub](https://github.com/Sunpeak-AI/filegrc).
|
package/template/WORKSPACE.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# {{
|
|
1
|
+
# {{program_title}}
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
{{program_summary}}
|
|
4
4
|
|
|
5
|
-
The workspace uses filegrc {{filegrc_version}} through the dependency
|
|
5
|
+
The workspace uses filegrc {{filegrc_version}} through the dependency spec `{{filegrc_version_range}}`.
|
|
6
6
|
|
|
7
7
|
## Work locally
|
|
8
8
|
|
|
@@ -20,22 +20,13 @@ Agents and terminal users can inspect the workspace without the browser:
|
|
|
20
20
|
|
|
21
21
|
```sh
|
|
22
22
|
npx filegrc guide --json
|
|
23
|
-
npx filegrc program-path --json
|
|
23
|
+
npx filegrc program-path --next --json
|
|
24
24
|
npx filegrc obligations --json
|
|
25
25
|
npx filegrc validate --json
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
Read `AGENTS.md` and `data/AGENTS.md` before broad changes.
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
The starter policies, controls, and obligations are proposals. They do not state that {{company_name}} operates the described controls.
|
|
33
|
-
|
|
34
|
-
1. Run `npx filegrc setup` for guided service and goal setup, or use browser onboarding. Then finish Step 1 by adding the real reviewers and operators, finishing the oversight team, and confirming applicable criteria, commitments, material vendors, and in-scope systems.
|
|
35
|
-
2. Review the starter policies, appoint a reviewer who is separate from the policy owner, and activate only the policies that match current practice. The reviewer will usually be another person in the organization, but may be external.
|
|
36
|
-
3. Review the starter control set, implement each applicable control with its actual procedure, scope, cadence, evidence sources, and implementation date, and confirm any linked Work Queue schedules are enabled. Marking a control implemented starts eligible schedules. Then record any complementary customer or subservice controls.
|
|
37
|
-
4. Open each generated External Evidence draft, choose its authoritative source System, collect the named artifact, and have another person verify it.
|
|
38
|
-
5. Run `npx filegrc program-readiness --require-ready`, record the management candidate period start when reliable evidence collection begins, maintain risk assessments and risks, update controls when needed, use Work Queue for scheduled work, and trigger Policy Events when changes create required actions. `npx filegrc obligations` previews every event task, owner, deadline, and requested proof before the trigger creates anything.
|
|
39
|
-
6. Engage a CPA firm, record the separate firm-agreed period in an audit record, review filegrc Evidence and External Evidence, and prepare fieldwork.
|
|
30
|
+
{{starter_setup}}
|
|
40
31
|
|
|
41
32
|
filegrc manages GRC records and audit evidence. It does not replace infrastructure logging, monitoring, identity, backup, endpoint, or incident-detection systems.
|
package/template/data/AGENTS.md
CHANGED
|
@@ -8,14 +8,14 @@ Treat the installed model as the authority. Do not infer a schema from a nearby
|
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
10
|
npx filegrc guide --json
|
|
11
|
-
npx filegrc program-path --json
|
|
11
|
+
npx filegrc program-path --next --json
|
|
12
12
|
npx filegrc types --json
|
|
13
13
|
npx filegrc guide RESOURCE_TYPE --json
|
|
14
14
|
npx filegrc list RESOURCE_TYPE --json
|
|
15
15
|
npx filegrc search "TERM" --json
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Use `program-path` to find the current lifecycle step and
|
|
18
|
+
Use `program-path --next --json` to find the current lifecycle step and first action. Use `--summary` for all six step statuses or `--current` for the current step’s full renderer Instructions, Use, Policy Basis, commands, and next actions. Use `guide` before any unfamiliar create or status transition. It repeats the page guidance and reports required fields, fields required by a status, enum values, relationship types and candidates, Markdown slots, timing, and exact paths. Use `describe` only when you need the raw model definition.
|
|
19
19
|
|
|
20
20
|
## Choose the right record
|
|
21
21
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
An evidence record explains what a proof item is, where it came from, what period it supports, who collected it, and which records or controls it supports. The attachment alone is not enough.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Onboarding does not create collection tests. After confirming applicable controls and authoritative source Systems, preview proposed tests with `npx filegrc evidence-test-drafts --preview --json`, then explicitly create the missing drafts. Risk assessments, meetings, vendor reviews, attestations, vulnerability scans, penetration tests, backup tests, exercises, exceptions, and findings do not need a separate test. When one of those operating records needs a fixed external artifact, create or update an External Evidence record for the artifact and link its ID from the operating record. Keep a created collection test as `draft` until the artifact has actually been captured. Set it to `collected` only after selecting the source System, attaching or referencing the result, and recording the source, date, classification, and collector. Set it to `verified` only after another named person checks it.
|
|
6
6
|
|
|
7
7
|
## Create evidence
|
|
8
8
|
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
"dataModelVersion": "1",
|
|
4
4
|
"id": "workspace",
|
|
5
5
|
"type": "workspace",
|
|
6
|
-
"title": "{{
|
|
6
|
+
"title": "{{program_title}}",
|
|
7
7
|
"organizationName": "{{company_name}}",
|
|
8
8
|
"timezone": "{{timezone}}",
|
|
9
|
-
"description": "
|
|
9
|
+
"description": "{{program_description}}",
|
|
10
10
|
"riskMethodology": {
|
|
11
11
|
"method": "5x5 likelihood and impact",
|
|
12
12
|
"likelihoodScale": ["Rare", "Unlikely", "Possible", "Likely", "Almost certain"],
|
package/template/package.json
CHANGED
package/template-parameters.json
CHANGED
|
@@ -45,6 +45,38 @@
|
|
|
45
45
|
{
|
|
46
46
|
"key": "filegrc_version_range",
|
|
47
47
|
"source": "resolved-filegrc-version"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"key": "program_title",
|
|
51
|
+
"source": "starter-profile"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"key": "program_description",
|
|
55
|
+
"source": "starter-profile"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"key": "program_summary",
|
|
59
|
+
"source": "starter-profile"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"key": "starter_setup",
|
|
63
|
+
"source": "starter-profile"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"key": "agent_title",
|
|
67
|
+
"source": "starter-profile"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"key": "agent_purpose",
|
|
71
|
+
"source": "starter-profile"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"key": "starter_baseline",
|
|
75
|
+
"source": "starter-profile"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"key": "audit_preparation_guidance",
|
|
79
|
+
"source": "starter-profile"
|
|
48
80
|
}
|
|
49
81
|
]
|
|
50
82
|
}
|