filegrc 0.16.3 → 0.16.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "filegrc",
3
- "version": "0.16.3",
3
+ "version": "0.16.5",
4
4
  "description": "Zero-dependency Git-native GRC engine",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/agent.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { modelSupports } from "../model/index.js";
2
2
  import { createResourceId } from "./id.js";
3
3
  import { markdownEntries } from "./resource-markdown.js";
4
- import { RESOURCE_INSTRUCTIONS, resourceProgramContext } from "./program-path.js";
4
+ import { RESOURCE_INSTRUCTIONS, RESOURCE_OUTPUTS, resourceProgramContext } from "./program-path.js";
5
5
  import { assessCollectionReview } from "./collection-review.js";
6
6
  import { resolveProgram } from "./program.js";
7
7
 
@@ -80,6 +80,7 @@ export function buildAgentGuide(loaded, type, options = {}) {
80
80
  title: definition.title,
81
81
  pluralTitle: definition.pluralTitle,
82
82
  instructions: RESOURCE_INSTRUCTIONS[type] || definition.description,
83
+ output: RESOURCE_OUTPUTS[type] || `A validated ${definition.title} record.`,
83
84
  use: definition.description,
84
85
  purpose: definition.description,
85
86
  programStep: resourceProgramContext(type, loaded.model),
@@ -69,8 +69,13 @@ export async function assessAuditPreparation(input, options = {}) {
69
69
  ? audits.find((record) => record.id === options.auditId)
70
70
  : options.selectDefault === false
71
71
  ? null
72
- : selectDefaultAudit(audits, calendarAsOf);
72
+ : selectDefaultAudit(options.programId && modelSupports(loaded.model, "program-scope")
73
+ ? audits.filter((record) => record.programId === options.programId)
74
+ : audits, calendarAsOf);
73
75
  if (options.auditId && !audit) throw new Error(`Audit "${options.auditId}" was not found.`);
76
+ if (audit && options.programId && modelSupports(loaded.model, "program-scope") && audit.programId !== options.programId) {
77
+ throw new Error(`Audit "${audit.id}" does not belong to Program "${options.programId}".`);
78
+ }
74
79
 
75
80
  const formalPeriodEnd = audit?.auditKind === "soc-2-type-2" ? coverageEnd(audit.coverage) : null;
76
81
  const readinessAsOf = formalPeriodEnd && formalPeriodEnd < calendarAsOf ? formalPeriodEnd : calendarAsOf;
package/src/cli.js CHANGED
@@ -289,10 +289,11 @@ export async function runCli(argv = process.argv.slice(2)) {
289
289
  const audit = auditId ? loaded.resources.find(({ id, type }) => id === auditId && type === "audit") : null;
290
290
  const programId = flags.program || audit?.programId;
291
291
  const readiness = await assessProgramReadiness(loaded, { asOf: flags["as-of"], programId });
292
- const auditReadiness = auditId ? await assessAuditPreparation(loaded, {
292
+ const auditReadiness = await assessAuditPreparation(loaded, {
293
293
  auditId,
294
- asOf: flags["as-of"]
295
- }) : null;
294
+ asOf: flags["as-of"],
295
+ programId
296
+ });
296
297
  const result = buildProgramPathResult(loaded.model, readiness, auditReadiness);
297
298
  const output = selectProgramPathOutput(result, flags);
298
299
  if (flags.json) console.log(JSON.stringify(output, null, 2));
@@ -458,15 +459,29 @@ export async function runCli(argv = process.argv.slice(2)) {
458
459
  if (command === "program-readiness") {
459
460
  const loaded = await loadWorkspace(root);
460
461
  const result = await assessProgramReadiness(loaded, { asOf: flags["as-of"], programId: flags.program });
461
- const output = flags.summary ? summarizeProgramReadiness(result) : result;
462
+ if (flags.control && flags.summary) throw new Error("Use either --control or --summary for program-readiness.");
463
+ const controlItem = flags.control
464
+ ? result.stages.find(({ id }) => id === "controls")?.items.find(({ resourceType, resourceId }) => (
465
+ resourceType === "control" && resourceId === flags.control
466
+ ))
467
+ : null;
468
+ if (flags.control && !controlItem) throw new Error(`Control "${flags.control}" is not selected in this Program.`);
469
+ const output = controlItem
470
+ ? { asOf: result.asOf, programId: result.program?.id, ...controlItem }
471
+ : flags.summary ? summarizeProgramReadiness(result) : result;
462
472
  const headlineStatus = result.progress.status || result.status;
463
473
  if (flags.json) console.log(JSON.stringify(output, null, 2));
474
+ else if (controlItem) {
475
+ console.log(`${controlItem.status.toUpperCase()}\t${controlItem.title}`);
476
+ if (!controlItem.nextSteps?.length) console.log(` ${controlItem.message}`);
477
+ printGuidanceSteps(controlItem);
478
+ }
464
479
  else if (flags.summary) {
465
480
  console.log(`${headlineStatus.toUpperCase()}: ${result.progress.label} ${result.progress.percent}%. ${result.progress.detail}`);
466
481
  for (const stage of output.stages) {
467
482
  console.log(`${stage.status.toUpperCase()}\t${stage.title}\t${stage.counts.action} checks need action`);
468
483
  }
469
- if (output.firstAction) console.log(`Next: ${output.firstAction.title}\t${output.firstAction.message}`);
484
+ if (output.firstAction) printNextAction(output.firstAction);
470
485
  }
471
486
  else {
472
487
  console.log(`${headlineStatus.toUpperCase()}: ${result.progress.label} ${result.progress.percent}%. ${result.progress.detail}`);
@@ -480,7 +495,10 @@ export async function runCli(argv = process.argv.slice(2)) {
480
495
  );
481
496
  for (const stage of result.stages) {
482
497
  console.log(`\n${stage.title}`);
483
- for (const item of stage.items) console.log(`${item.status.toUpperCase()}\t${item.title}\t${item.message}`);
498
+ for (const item of stage.items) {
499
+ console.log(`${item.status.toUpperCase()}\t${item.title}${item.nextSteps?.length ? "" : `\t${item.message}`}`);
500
+ printGuidanceSteps(item);
501
+ }
484
502
  }
485
503
  if (result.policyActivations.length) {
486
504
  console.log("\nPolicy activation assessments");
@@ -492,7 +510,7 @@ export async function runCli(argv = process.argv.slice(2)) {
492
510
  console.log(`\nEvidence Ready: management can start the candidate Type 2 period on or after ${result.suggestedCandidatePeriodStart || result.asOf}.`);
493
511
  }
494
512
  }
495
- if (flags["require-ready"] && !result.evidenceReady) process.exitCode = 2;
513
+ if (flags["require-ready"] && (controlItem ? controlItem.status !== "complete" : !result.evidenceReady)) process.exitCode = 2;
496
514
  return output;
497
515
  }
498
516
  if (command === "reporting-route-sets") {
@@ -1347,7 +1365,7 @@ Usage:
1347
1365
  filegrc list [resource-type] [--workflow] [--json]
1348
1366
  filegrc search <query> [--type resource-type] [--json]
1349
1367
  filegrc obligations [--program program-id] [--as-of YYYY-MM-DD] [--from YYYY-MM-DD] [--through YYYY-MM-DD] [--now RFC3339] [--complete] [--json]
1350
- filegrc program-readiness [--as-of YYYY-MM-DD] [--require-ready] [--summary] [--json]
1368
+ filegrc program-readiness [--as-of YYYY-MM-DD] [--require-ready] [--summary | --control CONTROL_ID] [--json]
1351
1369
  filegrc program-amendment <source-resource-id> [--json]
1352
1370
  filegrc review-bindings <retention-or-mapping-id> [--json]
1353
1371
  filegrc evidence-map [--as-of YYYY-MM-DD] [--json]
@@ -1365,7 +1383,7 @@ Usage:
1365
1383
  filegrc policy-library [--json | --accept proposal-id --proposal-revision revision --yes]
1366
1384
  filegrc trigger <event-type> (--occurred-on YYYY-MM-DD | --occurred-at RFC3339) [--program program-id] [--risk-level normal|high] [--subject resource-id[,resource-id]] [--title text] [--json]
1367
1385
  filegrc evidence-packet [--audit audit-id | --program program-id] [--start YYYY-MM-DD] [--end YYYY-MM-DD] [--output .filegrc/path] [--preview] [--require-ready] [--json]
1368
- filegrc get [resource-type] <id> [--mutation]
1386
+ filegrc get [resource-type] <id> [--mutation|--workflow]
1369
1387
  filegrc references <id> [--json]
1370
1388
  filegrc preview-mutation <preview.json|-> [--json]
1371
1389
  filegrc create <mutation.json|-> [--json]
@@ -1496,8 +1514,9 @@ source. No audit ID or CPA firm is required.
1496
1514
  Options:
1497
1515
  --program <id> Program to assess when more than one active Program exists
1498
1516
  --as-of <date> Evaluate effective dates and obligations on YYYY-MM-DD
1499
- --require-ready Exit with code 2 unless the Evidence Ready gate passes
1517
+ --require-ready Exit with code 2 unless the selected Control, or the full program, is ready
1500
1518
  --summary Omit item details and print stage counts and next actions
1519
+ --control <id> Show the current setup steps for one selected Control
1501
1520
  --json Print the result as JSON
1502
1521
  --root <path> Workspace path
1503
1522
  --help Show this help`);
@@ -1749,6 +1768,7 @@ function printAgentGuide(result) {
1749
1768
  console.log(`Program step: ${result.programStep.order ? `Step ${result.programStep.order}` : `Step ${result.programStep.number}`} · ${result.programStep.title}`);
1750
1769
  }
1751
1770
  console.log(`Instructions: ${result.instructions}`);
1771
+ console.log(`Output: ${result.output}`);
1752
1772
  console.log(`Use: ${result.use}`);
1753
1773
  console.log(`Policy basis: ${result.policyBasis}`);
1754
1774
  console.log(`Timing: ${result.cadence}`);
@@ -1810,23 +1830,21 @@ function buildProgramPathResult(model, readiness, auditReadiness) {
1810
1830
  const readinessById = new Map(readiness.stages.map((stage) => [stage.id, stage]));
1811
1831
  const stages = buildAgentProgramPath(model).map((stage) => {
1812
1832
  if (stage.id === "audit") {
1813
- const auditAction = auditReadiness?.firstAction || (
1814
- auditReadiness?.status === "not-started"
1815
- ? {
1816
- id: "create-audit",
1817
- status: "action",
1818
- title: "Create the planned CPA engagement",
1819
- message: "Create a planned Audit from the current management scope, then replace the remaining scaffold values with the CPA firm and firm-agreed scope and dates.",
1820
- resourceType: "audit",
1821
- commands: [
1822
- "npx filegrc guide audit --json",
1823
- "npx filegrc scaffold audit --title \"YEAR SOC 2 TYPE\" > audit-mutation.json",
1824
- "npx filegrc create audit-mutation.json --json",
1825
- "npx filegrc prepare-audit AUDIT_ID --json"
1826
- ]
1827
- }
1828
- : null
1829
- );
1833
+ const auditAction = !auditReadiness || auditReadiness.status === "not-started"
1834
+ ? {
1835
+ id: "create-audit",
1836
+ status: "action",
1837
+ title: "Record the CPA engagement",
1838
+ message: "After engaging the CPA firm, create an Audit with the firm-agreed type, scope, and dates.",
1839
+ resourceType: "audit",
1840
+ commands: [
1841
+ "npx filegrc guide audit --json",
1842
+ "npx filegrc scaffold audit --title \"YEAR SOC 2 TYPE\" > audit-mutation.json",
1843
+ "npx filegrc create audit-mutation.json --json",
1844
+ "npx filegrc prepare-audit AUDIT_ID --json"
1845
+ ]
1846
+ }
1847
+ : auditReadiness?.stages?.flatMap(({ items }) => items).find(({ status }) => status === "action") || null;
1830
1848
  return {
1831
1849
  ...stage,
1832
1850
  status: auditReadiness?.status || "not-started",
@@ -1866,12 +1884,29 @@ function buildProgramPathResult(model, readiness, auditReadiness) {
1866
1884
  };
1867
1885
  }
1868
1886
 
1887
+ function printGuidanceSteps(item) {
1888
+ item.nextSteps?.forEach((step, index) => console.log(` ${index + 1}. ${step}`));
1889
+ }
1890
+
1891
+ function printNextAction(action) {
1892
+ if (action.nextSteps?.length) {
1893
+ console.log(`Next: ${action.title}`);
1894
+ printGuidanceSteps(action);
1895
+ } else {
1896
+ console.log(`Next: ${action.title} · ${action.message}`);
1897
+ }
1898
+ }
1899
+
1869
1900
  function printProgramPath(result) {
1870
1901
  console.log(`Current: Step ${result.currentStep.number}, ${result.currentStep.title}`);
1871
1902
  console.log(`Evidence Ready: ${result.evidenceReady ? "yes" : "no"}; operating: ${result.operating ? "yes" : "no"}`);
1872
1903
  for (const stage of result.stages) {
1873
1904
  console.log(`\nStep ${stage.number}. ${stage.title} [${String(stage.status).toUpperCase()}]`);
1874
1905
  console.log(stage.summary);
1906
+ for (const section of stage.sections) {
1907
+ console.log(` ${section.title}`);
1908
+ section.steps.forEach((step, index) => console.log(` ${index + 1}. ${step}`));
1909
+ }
1875
1910
  for (const page of stage.pages) {
1876
1911
  console.log(`${page.order ? `Step ${page.order}` : "Operating area"} · ${page.title} (${page.type || `utility:${page.utility}`})`);
1877
1912
  console.log(` ${page.summary}`);
@@ -1885,7 +1920,7 @@ function printProgramPath(result) {
1885
1920
  }
1886
1921
  console.log("Commands:");
1887
1922
  for (const command of stage.commands) console.log(` ${command}`);
1888
- for (const action of stage.nextActions) console.log(`Next: ${action.title} · ${action.message}`);
1923
+ for (const action of stage.nextActions) printNextAction(action);
1889
1924
  }
1890
1925
  }
1891
1926
 
@@ -1953,6 +1988,7 @@ function summarizePathAction(action) {
1953
1988
  status: action.status,
1954
1989
  title: action.title,
1955
1990
  message: action.message,
1991
+ ...(action.nextSteps?.length ? { nextSteps: action.nextSteps } : {}),
1956
1992
  ...(action.resourceType ? { resourceType: action.resourceType } : {}),
1957
1993
  ...(action.resourceId ? { resourceId: action.resourceId } : {})
1958
1994
  };
@@ -1965,11 +2001,11 @@ function nextActionCommands(stage, action) {
1965
2001
  const commands = [`npx filegrc guide ${resourceType} --json`];
1966
2002
  if (action.resourceId) {
1967
2003
  const resourceId = shellArgument(action.resourceId);
1968
- commands.push(`npx filegrc get ${resourceId} --mutation`);
2004
+ commands.push(`npx filegrc get ${resourceId} --mutation > MUTATION.json`);
1969
2005
  commands.push(`npx filegrc update ${resourceType} ${resourceId} MUTATION.json --json`);
1970
2006
  } else {
1971
2007
  commands.push(`npx filegrc list ${resourceType} --json`);
1972
- commands.push(`npx filegrc scaffold ${resourceType} --title "NAME"`);
2008
+ commands.push(`npx filegrc scaffold ${resourceType} --title "NAME" > MUTATION.json`);
1973
2009
  commands.push("npx filegrc create MUTATION.json --json");
1974
2010
  }
1975
2011
  return commands;
@@ -1998,13 +2034,13 @@ function printProgramPathOutput(result, flags) {
1998
2034
  console.log(`${String(stage.status).toUpperCase()}\tStep ${stage.number}\t${stage.title}`);
1999
2035
  }
2000
2036
  const current = result.stages.find(({ id }) => id === result.currentStep.id);
2001
- if (current?.nextAction) console.log(`Next: ${current.nextAction.title} · ${current.nextAction.message}`);
2037
+ if (current?.nextAction) printNextAction(current.nextAction);
2002
2038
  return;
2003
2039
  }
2004
2040
  if (flags.next) {
2005
2041
  console.log(`Current: Step ${result.currentStep.number}, ${result.currentStep.title}`);
2006
2042
  if (result.step?.nextAction) {
2007
- console.log(`Next: ${result.step.nextAction.title} · ${result.step.nextAction.message}`);
2043
+ printNextAction(result.step.nextAction);
2008
2044
  }
2009
2045
  for (const command of result.step?.commands || []) console.log(` ${command}`);
2010
2046
  return;
@@ -2020,7 +2056,8 @@ function printWorkflow(result) {
2020
2056
  console.log(`\n${result.counts.findings.ready || 0} ready findings, ${result.counts.workItems.overdue || 0} overdue Work Items`);
2021
2057
  if (result.recommended) {
2022
2058
  console.log(`Next: ${result.recommended.title}`);
2023
- if (result.recommended.message) console.log(` ${result.recommended.message}`);
2059
+ if (!result.recommended.nextSteps?.length && result.recommended.message) console.log(` ${result.recommended.message}`);
2060
+ printGuidanceSteps(result.recommended);
2024
2061
  const command = result.recommended.nextAction?.command || result.recommended.actions?.[0]?.command;
2025
2062
  if (command) console.log(` ${command}`);
2026
2063
  }
@@ -2035,6 +2072,7 @@ function summarizeProgramReadiness(result) {
2035
2072
  status: item.status,
2036
2073
  title: item.title,
2037
2074
  ...(options.message === false ? {} : { message: item.message }),
2075
+ ...(item.nextSteps?.length ? { nextSteps: item.nextSteps } : {}),
2038
2076
  ...(item.resourceType ? { resourceType: item.resourceType } : {}),
2039
2077
  ...(item.resourceId ? { resourceId: item.resourceId } : {})
2040
2078
  } : null;
package/src/index.js CHANGED
@@ -104,6 +104,7 @@ export {
104
104
  buildAgentProgramPath,
105
105
  PROGRAM_PATH,
106
106
  RESOURCE_INSTRUCTIONS,
107
+ RESOURCE_OUTPUTS,
107
108
  RESOURCE_PAGE_SUMMARIES,
108
109
  resourceProgramContext
109
110
  } from "./program-path.js";